Merge pull request #1357 from Kotlin/version-1.3.0-RC
Version 1.3.0-RC
diff --git a/CHANGES.md b/CHANGES.md
index a8f1106..cd920aa 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,28 @@
# Change log for kotlinx.coroutines
+## Version 1.3.0-RC
+
+### Flow
+
+* Core `Flow` API is promoted to stable
+* New basic `Flow` operators: `withIndex`, `collectIndexed`, `distinctUntilChanged` overload
+* New core `Flow` operators: `onStart` and `onCompletion`
+* `ReceiveChannel.consumeAsFlow` and `emitAll` (#1340)
+
+### General changes
+
+* Kotlin updated to 1.3.41
+* Added `kotlinx-coroutines-bom` with Maven Bill of Materials (#1110)
+* Reactive integrations are seriously improved
+ * All builders now are top-level functions instead of extensions on `CoroutineScope` and prohibit `Job` instance in their context to simplify lifecycle management
+ * Fatal exceptions are handled consistently (#1297)
+ * Integration with Reactor Context added (#284)
+* Stacktrace recovery for `suspend fun main` (#1328)
+* `CoroutineScope.cancel` extension with message (#1338)
+* Protection against non-monotonic clocks in `delay` (#1312)
+* `Duration.ZERO` is handled properly in JDK 8 extensions (#1349)
+* Library code is adjusted to be more minification-friendly
+
## Version 1.3.0-M2
* Kotlin updated to 1.3.40.
diff --git a/README.md b/README.md
index f3c5558..84083a8 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,10 @@
[![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.3.0-M2) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.3.0-M2)
+[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=1.3.0-RC) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.3.0-RC)
Library support for Kotlin coroutines with [multiplatform](#multiplatform) support.
-This is a companion version for Kotlin `1.3.40` release.
+This is a companion version for Kotlin `1.3.41` release.
```kotlin
suspend fun main() = coroutineScope {
@@ -81,7 +81,7 @@
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
- <version>1.3.0-M2</version>
+ <version>1.3.0-RC</version>
</dependency>
```
@@ -89,7 +89,7 @@
```xml
<properties>
- <kotlin.version>1.3.40</kotlin.version>
+ <kotlin.version>1.3.41</kotlin.version>
</properties>
```
@@ -99,7 +99,7 @@
```groovy
dependencies {
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M2'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-RC'
}
```
@@ -107,7 +107,7 @@
```groovy
buildscript {
- ext.kotlin_version = '1.3.40'
+ ext.kotlin_version = '1.3.41'
}
```
@@ -125,7 +125,7 @@
```groovy
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M2")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-RC")
}
```
@@ -133,7 +133,7 @@
```groovy
plugins {
- kotlin("jvm") version "1.3.40"
+ kotlin("jvm") version "1.3.41"
}
```
@@ -144,7 +144,7 @@
Core modules of `kotlinx.coroutines` are also available for
[Kotlin/JS](#js) and [Kotlin/Native](#native).
In common code that should get compiled for different platforms, add dependency to
-[`kotlinx-coroutines-core-common`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-common/1.3.0-M2/jar)
+[`kotlinx-coroutines-core-common`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-common/1.3.0-RC/jar)
(follow the link to get the dependency declaration snippet).
### Android
@@ -153,7 +153,7 @@
module as dependency when using `kotlinx.coroutines` on Android:
```groovy
-implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-M2'
+implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-RC'
```
This gives you access to Android [Dispatchers.Main](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-android/kotlinx.coroutines.android/kotlinx.coroutines.-dispatchers/index.html)
@@ -172,7 +172,7 @@
### 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.3.0-M2/jar)
+[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.3.0-RC/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.
@@ -180,7 +180,7 @@
### 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.3.0-M2/jar)
+[`kotlinx-coroutines-core-native`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-native/1.3.0-RC/jar)
(follow the link to get the dependency declaration snippet).
Only single-threaded code (JS-style) on Kotlin/Native is currently supported.
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt
index 94df09a..3e20e88 100644
--- a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt
@@ -202,7 +202,9 @@
public final class kotlinx/coroutines/CoroutineScopeKt {
public static final fun CoroutineScope (Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/CoroutineScope;
public static final fun MainScope ()Lkotlinx/coroutines/CoroutineScope;
+ public static final fun cancel (Lkotlinx/coroutines/CoroutineScope;Ljava/lang/String;Ljava/lang/Throwable;)V
public static final fun cancel (Lkotlinx/coroutines/CoroutineScope;Ljava/util/concurrent/CancellationException;)V
+ public static synthetic fun cancel$default (Lkotlinx/coroutines/CoroutineScope;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)V
public static synthetic fun cancel$default (Lkotlinx/coroutines/CoroutineScope;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V
public static final fun coroutineScope (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun ensureActive (Lkotlinx/coroutines/CoroutineScope;)V
@@ -352,8 +354,10 @@
public static final synthetic fun cancel (Lkotlin/coroutines/CoroutineContext;)V
public static final synthetic fun cancel (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)Z
public static final fun cancel (Lkotlin/coroutines/CoroutineContext;Ljava/util/concurrent/CancellationException;)V
+ public static final fun cancel (Lkotlinx/coroutines/Job;Ljava/lang/String;Ljava/lang/Throwable;)V
public static synthetic fun cancel$default (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;ILjava/lang/Object;)Z
public static synthetic fun cancel$default (Lkotlin/coroutines/CoroutineContext;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V
+ public static synthetic fun cancel$default (Lkotlinx/coroutines/Job;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)V
public static final fun cancelAndJoin (Lkotlinx/coroutines/Job;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final synthetic fun cancelChildren (Lkotlin/coroutines/CoroutineContext;)V
public static final synthetic fun cancelChildren (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V
@@ -437,6 +441,7 @@
public fun join (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun plus (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
public fun start ()Z
+ public fun toString ()Ljava/lang/String;
}
public final class kotlinx/coroutines/NonDisposableHandle : kotlinx/coroutines/ChildHandle, kotlinx/coroutines/DisposableHandle {
@@ -670,7 +675,9 @@
public static final fun minWith (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Comparator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun none (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun none (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun onReceiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/selects/SelectClause1;
public static final fun partition (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun receiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun reduce (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun reduceIndexed (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun requireNoNulls (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/channels/ReceiveChannel;
@@ -743,12 +750,14 @@
public abstract synthetic fun cancel (Ljava/lang/Throwable;)Z
public abstract fun cancel (Ljava/util/concurrent/CancellationException;)V
public abstract fun getOnReceive ()Lkotlinx/coroutines/selects/SelectClause1;
+ public abstract fun getOnReceiveOrClosed ()Lkotlinx/coroutines/selects/SelectClause1;
public abstract fun getOnReceiveOrNull ()Lkotlinx/coroutines/selects/SelectClause1;
public abstract fun isClosedForReceive ()Z
public abstract fun isEmpty ()Z
public abstract fun iterator ()Lkotlinx/coroutines/channels/ChannelIterator;
public abstract fun poll ()Ljava/lang/Object;
public abstract fun receive (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun receiveOrClosed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun receiveOrNull (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
@@ -784,6 +793,23 @@
public static fun values ()[Lkotlinx/coroutines/channels/TickerMode;
}
+public final class kotlinx/coroutines/channels/ValueOrClosed {
+ public static final field Companion Lkotlinx/coroutines/channels/ValueOrClosed$Companion;
+ public static final synthetic fun box-impl (Ljava/lang/Object;)Lkotlinx/coroutines/channels/ValueOrClosed;
+ public fun equals (Ljava/lang/Object;)Z
+ public static fun equals-impl (Ljava/lang/Object;Ljava/lang/Object;)Z
+ public static final fun equals-impl0 (Ljava/lang/Object;Ljava/lang/Object;)Z
+ public static final fun getCloseCause-impl (Ljava/lang/Object;)Ljava/lang/Throwable;
+ public static final fun getValue-impl (Ljava/lang/Object;)Ljava/lang/Object;
+ public static final fun getValueOrNull-impl (Ljava/lang/Object;)Ljava/lang/Object;
+ public fun hashCode ()I
+ public static fun hashCode-impl (Ljava/lang/Object;)I
+ public static final fun isClosed-impl (Ljava/lang/Object;)Z
+ public fun toString ()Ljava/lang/String;
+ public static fun toString-impl (Ljava/lang/Object;)Ljava/lang/String;
+ public final synthetic fun unbox-impl ()Ljava/lang/Object;
+}
+
public abstract class kotlinx/coroutines/flow/AbstractFlow : kotlinx/coroutines/flow/Flow {
public fun <init> ()V
public final fun collect (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -800,6 +826,9 @@
public final class kotlinx/coroutines/flow/FlowKt {
public static final field DEFAULT_CONCURRENCY_PROPERTY_NAME Ljava/lang/String;
+ public static final fun BehaviourSubject ()Ljava/lang/Object;
+ public static final fun PublishSubject ()Ljava/lang/Object;
+ public static final fun ReplaySubject ()Ljava/lang/Object;
public static final fun asFlow (Ljava/lang/Iterable;)Lkotlinx/coroutines/flow/Flow;
public static final fun asFlow (Ljava/util/Iterator;)Lkotlinx/coroutines/flow/Flow;
public static final fun asFlow (Lkotlin/jvm/functions/Function0;)Lkotlinx/coroutines/flow/Flow;
@@ -820,22 +849,30 @@
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 fun collectIndexed (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun combineLatest (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
public static final fun combineLatest (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function4;)Lkotlinx/coroutines/flow/Flow;
public static final fun combineLatest (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function5;)Lkotlinx/coroutines/flow/Flow;
public static final fun combineLatest (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function6;)Lkotlinx/coroutines/flow/Flow;
public static final fun combineLatest (Lkotlinx/coroutines/flow/Flow;[Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final synthetic fun combineLatest (Lkotlinx/coroutines/flow/Flow;[Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun compose (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun concatMap (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun concatWith (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun concatWith (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
public static final fun conflate (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun consumeAsFlow (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/flow/Flow;
public static final fun count (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun count (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun debounce (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow;
public static final fun delayEach (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow;
public static final fun delayFlow (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow;
public static final fun distinctUntilChanged (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun distinctUntilChanged (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final fun distinctUntilChangedBy (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
public static final fun drop (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
public static final fun dropWhile (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun emitAll (Lkotlinx/coroutines/flow/FlowCollector;Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun emitAll (Lkotlinx/coroutines/flow/FlowCollector;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun emptyFlow ()Lkotlinx/coroutines/flow/Flow;
public static final fun filter (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
@@ -844,9 +881,11 @@
public static final fun filterNotNull (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
public static final fun first (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun first (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun flatMap (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final fun flatMapConcat (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final fun flatMapMerge (Lkotlinx/coroutines/flow/Flow;ILkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static synthetic fun flatMapMerge$default (Lkotlinx/coroutines/flow/Flow;ILkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun flatten (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
public static final fun flattenConcat (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
public static final fun flattenMerge (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
public static synthetic fun flattenMerge$default (Lkotlinx/coroutines/flow/Flow;IILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
@@ -859,15 +898,26 @@
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
public static final fun launchIn (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/Job;
public static final fun map (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final fun mapNotNull (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
- public static final fun onCompletion (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun merge (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun observeOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow;
+ public static final synthetic fun onCompletion (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
+ 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 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;
+ public static final fun onErrorReturn (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
+ public static synthetic fun onErrorReturn$default (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun onStart (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final fun produceIn (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;)Lkotlinx/coroutines/channels/ReceiveChannel;
+ public static final fun publishOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow;
public static final fun reduce (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
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;
@@ -876,9 +926,17 @@
public static final fun retryWhen (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function4;)Lkotlinx/coroutines/flow/Flow;
public static final fun sample (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow;
public static final fun scan (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun scanFold (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
public static final fun scanReduce (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
public static final fun single (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun singleOrNull (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun skip (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
+ public static final fun startWith (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun startWith (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun subscribe (Lkotlinx/coroutines/flow/Flow;)V
+ public static final fun subscribe (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)V
+ public static final fun subscribe (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V
+ public static final fun subscribeOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow;
public static final fun switchMap (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final fun take (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
public static final fun takeWhile (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
@@ -888,34 +946,10 @@
public static final fun toSet (Lkotlinx/coroutines/flow/Flow;Ljava/util/Set;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun toSet$default (Lkotlinx/coroutines/flow/Flow;Ljava/util/Set;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static final fun transform (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
- public static final fun unsafeFlow (Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
- public static final fun zip (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
-}
-
-public final class kotlinx/coroutines/flow/MigrationKt {
- public static final fun BehaviourSubject ()Ljava/lang/Object;
- public static final fun PublishSubject ()Ljava/lang/Object;
- public static final fun ReplaySubject ()Ljava/lang/Object;
- public static final fun compose (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
- public static final fun concatMap (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
- public static final fun flatMap (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
- public static final fun flatten (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
- public static final fun forEach (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)V
- public static final fun merge (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
- public static final fun observeOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)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;
- public static final fun onErrorReturn (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
- public static synthetic fun onErrorReturn$default (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
- public static final fun publishOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow;
- public static final fun scanFold (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
- public static final fun skip (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
- public static final fun subscribe (Lkotlinx/coroutines/flow/Flow;)V
- public static final fun subscribe (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)V
- public static final fun subscribe (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V
- public static final fun subscribeOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun unsafeTransform (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
public static final fun withContext (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function1;)V
+ public static final fun withIndex (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
+ public static final fun zip (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
}
public abstract class kotlinx/coroutines/flow/internal/ChannelFlow : kotlinx/coroutines/flow/Flow {
@@ -923,7 +957,7 @@
public final field context Lkotlin/coroutines/CoroutineContext;
public fun <init> (Lkotlin/coroutines/CoroutineContext;I)V
public fun additionalToStringProps ()Ljava/lang/String;
- public final fun broadcastImpl (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineStart;)Lkotlinx/coroutines/channels/BroadcastChannel;
+ public fun broadcastImpl (Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineStart;)Lkotlinx/coroutines/channels/BroadcastChannel;
public fun collect (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected abstract fun collectTo (Lkotlinx/coroutines/channels/ProducerScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
protected abstract fun create (Lkotlin/coroutines/CoroutineContext;I)Lkotlinx/coroutines/flow/internal/ChannelFlow;
@@ -933,9 +967,12 @@
public static synthetic fun update$default (Lkotlinx/coroutines/flow/internal/ChannelFlow;Lkotlin/coroutines/CoroutineContext;IILjava/lang/Object;)Lkotlinx/coroutines/flow/internal/ChannelFlow;
}
-public final class kotlinx/coroutines/flow/internal/SafeCollector : kotlinx/coroutines/flow/FlowCollector {
- public fun <init> (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/CoroutineContext;)V
- public fun emit (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+public final class kotlinx/coroutines/flow/internal/FlowExceptions_commonKt {
+ public static final fun checkIndexOverflow (I)I
+}
+
+public final class kotlinx/coroutines/flow/internal/SafeCollectorKt {
+ public static final fun unsafeFlow (Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
}
public final class kotlinx/coroutines/flow/internal/SendingCollector : kotlinx/coroutines/flow/internal/ConcurrentFlowCollector {
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactive.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactive.txt
index 2afa313..643f641 100644
--- a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactive.txt
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactive.txt
@@ -20,8 +20,28 @@
}
public final class kotlinx/coroutines/reactive/PublishKt {
+ public static final fun publish (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lorg/reactivestreams/Publisher;
public static final fun publish (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lorg/reactivestreams/Publisher;
+ public static synthetic fun publish$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lorg/reactivestreams/Publisher;
public static synthetic fun publish$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lorg/reactivestreams/Publisher;
+ public static final fun publishInternal (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lorg/reactivestreams/Publisher;
+}
+
+public final class kotlinx/coroutines/reactive/PublisherCoroutine : kotlinx/coroutines/AbstractCoroutine, kotlinx/coroutines/channels/ProducerScope, kotlinx/coroutines/selects/SelectClause2, org/reactivestreams/Subscription {
+ public fun <init> (Lkotlin/coroutines/CoroutineContext;Lorg/reactivestreams/Subscriber;)V
+ public fun cancel ()V
+ public fun close (Ljava/lang/Throwable;)Z
+ public fun getChannel ()Lkotlinx/coroutines/channels/SendChannel;
+ public fun getOnSend ()Lkotlinx/coroutines/selects/SelectClause2;
+ public fun invokeOnClose (Lkotlin/jvm/functions/Function1;)Ljava/lang/Void;
+ public synthetic fun invokeOnClose (Lkotlin/jvm/functions/Function1;)V
+ public fun isClosedForSend ()Z
+ public fun isFull ()Z
+ public fun offer (Ljava/lang/Object;)Z
+ public synthetic fun onCompleted (Ljava/lang/Object;)V
+ public fun registerSelectClause2 (Lkotlinx/coroutines/selects/SelectInstance;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V
+ public fun request (J)V
+ public fun send (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class kotlinx/coroutines/reactive/flow/FlowAsPublisherKt {
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactor.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactor.txt
index 8afd014..46b35ed 100644
--- a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactor.txt
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-reactor.txt
@@ -6,15 +6,32 @@
}
public final class kotlinx/coroutines/reactor/FluxKt {
+ public static final fun flux (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Flux;
public static final fun flux (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Flux;
+ public static synthetic fun flux$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lreactor/core/publisher/Flux;
public static synthetic fun flux$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lreactor/core/publisher/Flux;
}
public final class kotlinx/coroutines/reactor/MonoKt {
+ public static final fun mono (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Mono;
public static final fun mono (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Mono;
+ public static synthetic fun mono$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lreactor/core/publisher/Mono;
public static synthetic fun mono$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lreactor/core/publisher/Mono;
}
+public final class kotlinx/coroutines/reactor/ReactorContext : kotlin/coroutines/AbstractCoroutineContextElement {
+ public static final field Key Lkotlinx/coroutines/reactor/ReactorContext$Key;
+ public fun <init> (Lreactor/util/context/Context;)V
+ public final fun getContext ()Lreactor/util/context/Context;
+}
+
+public final class kotlinx/coroutines/reactor/ReactorContext$Key : kotlin/coroutines/CoroutineContext$Key {
+}
+
+public final class kotlinx/coroutines/reactor/ReactorContextKt {
+ public static final fun asCoroutineContext (Lreactor/util/context/Context;)Lkotlinx/coroutines/reactor/ReactorContext;
+}
+
public final class kotlinx/coroutines/reactor/SchedulerCoroutineDispatcher : kotlinx/coroutines/CoroutineDispatcher, kotlinx/coroutines/Delay {
public fun <init> (Lreactor/core/scheduler/Scheduler;)V
public fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-rx2.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-rx2.txt
index 67ef8a1..54a9663 100644
--- a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-rx2.txt
+++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-rx2.txt
@@ -21,7 +21,9 @@
}
public final class kotlinx/coroutines/rx2/RxCompletableKt {
+ public static final fun rxCompletable (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Completable;
public static final fun rxCompletable (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Completable;
+ public static synthetic fun rxCompletable$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Completable;
public static synthetic fun rxCompletable$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Completable;
}
@@ -35,17 +37,23 @@
}
public final class kotlinx/coroutines/rx2/RxFlowableKt {
+ public static final fun rxFlowable (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Flowable;
public static final fun rxFlowable (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Flowable;
+ public static synthetic fun rxFlowable$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Flowable;
public static synthetic fun rxFlowable$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Flowable;
}
public final class kotlinx/coroutines/rx2/RxMaybeKt {
+ public static final fun rxMaybe (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Maybe;
public static final fun rxMaybe (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Maybe;
+ public static synthetic fun rxMaybe$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Maybe;
public static synthetic fun rxMaybe$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Maybe;
}
public final class kotlinx/coroutines/rx2/RxObservableKt {
+ public static final fun rxObservable (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Observable;
public static final fun rxObservable (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Observable;
+ public static synthetic fun rxObservable$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Observable;
public static synthetic fun rxObservable$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Observable;
}
@@ -54,7 +62,9 @@
}
public final class kotlinx/coroutines/rx2/RxSingleKt {
+ public static final fun rxSingle (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Single;
public static final fun rxSingle (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lio/reactivex/Single;
+ public static synthetic fun rxSingle$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Single;
public static synthetic fun rxSingle$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/reactivex/Single;
}
diff --git a/binary-compatibility-validator/resources/api.properties b/binary-compatibility-validator/resources/api.properties
index 690989a..9fa115b 100644
--- a/binary-compatibility-validator/resources/api.properties
+++ b/binary-compatibility-validator/resources/api.properties
@@ -4,6 +4,6 @@
module.roots=/ integration reactive ui
module.marker=build.gradle
-module.ignore=kotlinx-coroutines-rx-example stdlib-stubs benchmarks knit binary-compatibility-validator site publication-validator
+module.ignore=kotlinx-coroutines-rx-example stdlib-stubs benchmarks knit binary-compatibility-validator site publication-validator kotlinx-coroutines-bom
packages.internal=kotlinx.coroutines.internal
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index a1813d1..4bccce7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,8 +8,8 @@
def rootModule = "kotlinx.coroutines"
def coreModule = "kotlinx-coroutines-core"
// Not applicable for Kotlin plugin
-def sourceless = ['kotlinx.coroutines', 'site']
-def internal = sourceless + ['benchmarks', 'knit', 'js-stub', 'stdlib-stubs', 'binary-compatibility-validator']
+def sourceless = ['kotlinx.coroutines', 'site', 'kotlinx-coroutines-bom']
+def internal = ['kotlinx.coroutines', 'site', 'benchmarks', 'knit', 'js-stub', 'stdlib-stubs', 'binary-compatibility-validator']
// Not published
def unpublished = internal + ['kotlinx-coroutines-rx-example', 'example-frontend-js', 'android-unit-tests']
@@ -58,12 +58,12 @@
}
dependencies {
- classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version"
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$bintray_version"
classpath "com.moowork.gradle:gradle-node-plugin:$gradle_node_version"
+ classpath "io.spring.gradle:dependency-management-plugin:$spring_dependency_management_version"
// JMH plugins
classpath "com.github.jengelman.gradle.plugins:shadow:4.0.2"
@@ -95,6 +95,8 @@
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}
}
+
+ ext.unpublished = unpublished
}
allprojects {
@@ -123,7 +125,6 @@
def platform = platformOf(it)
apply from: rootProject.file("gradle/compile-${platform}.gradle")
-
dependencies {
// See comment below for rationale, it will be replaced with "project" dependency
compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version"
@@ -135,6 +136,7 @@
tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
kotlinOptions.freeCompilerArgs += experimentalAnnotations.collect { "-Xuse-experimental=" + it }
kotlinOptions.freeCompilerArgs += "-progressive"
+ kotlinOptions.freeCompilerArgs += "-XXLanguage:+InlineClasses"
// Binary compatibility support
kotlinOptions.freeCompilerArgs += ["-Xdump-declarations-to=${buildDir}/visibilities.json"]
}
@@ -201,23 +203,27 @@
def core_docs_file = "$projectDir/kotlinx-coroutines-core/build/dokka/kotlinx-coroutines-core/package-list"
configure(subprojects.findAll { !unpublished.contains(it.name) }) {
- apply from: rootProject.file('gradle/dokka.gradle')
+ if (it.name != 'kotlinx-coroutines-bom') {
+ apply from: rootProject.file('gradle/dokka.gradle')
+ }
apply from: rootProject.file('gradle/publish-bintray.gradle')
}
configure(subprojects.findAll { !unpublished.contains(it.name) }) {
- if (it.name != coreModule) {
- dokka.dependsOn project(":$coreModule").dokka
- tasks.withType(dokka.getClass()) {
- externalDocumentationLink {
- url = new URL(core_docs_url)
- packageListUrl = new URL("file://$core_docs_file")
+ if (it.name != "kotlinx-coroutines-bom") {
+ if (it.name != coreModule) {
+ dokka.dependsOn project(":$coreModule").dokka
+ tasks.withType(dokka.getClass()) {
+ externalDocumentationLink {
+ url = new URL(core_docs_url)
+ packageListUrl = new URL("file://$core_docs_file")
+ }
}
}
- }
- if (platformOf(it) == "jvm") {
- dokkaJavadoc.dependsOn project(":$coreModule").dokka
+ if (platformOf(it) == "jvm") {
+ dokkaJavadoc.dependsOn project(":$coreModule").dokka
+ }
}
}
diff --git a/common/kotlinx-coroutines-core-common/test/channels/ChannelReceiveOrClosedTest.kt b/common/kotlinx-coroutines-core-common/test/channels/ChannelReceiveOrClosedTest.kt
new file mode 100644
index 0000000..303e6d1
--- /dev/null
+++ b/common/kotlinx-coroutines-core-common/test/channels/ChannelReceiveOrClosedTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.channels
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class ChannelReceiveOrClosedTest : TestBase() {
+ @Test
+ fun testChannelOfThrowables() = runTest {
+ val channel = Channel<Throwable>()
+ launch {
+ channel.send(TestException1())
+ channel.close(TestException2())
+ }
+
+ val element = channel.receiveOrClosed()
+ assertTrue(element.isValue)
+ assertTrue(element.value is TestException1)
+ assertTrue(element.valueOrNull is TestException1)
+
+ val closed = channel.receiveOrClosed()
+ assertTrue(closed.isClosed)
+ assertTrue(closed.closeCause is TestException2)
+ }
+
+ @Test
+ @Suppress("ReplaceAssertBooleanWithAssertEquality") // inline classes test
+ fun testNullableIntChanel() = runTest {
+ val channel = Channel<Int?>()
+ launch {
+ expect(2)
+ channel.send(1)
+ expect(3)
+ channel.send(null)
+
+ expect(6)
+ channel.close()
+ }
+
+ expect(1)
+ val element = channel.receiveOrClosed()
+ assertTrue(element.isValue)
+ assertEquals(1, element.value)
+ assertEquals(1, element.valueOrNull)
+ assertEquals("Value(1)", element.toString())
+ assertTrue(ValueOrClosed.value(1) == element) // Don't box
+
+ expect(4)
+ val nullElement = channel.receiveOrClosed()
+ assertTrue(nullElement.isValue)
+ assertNull(nullElement.value)
+ assertNull(nullElement.valueOrNull)
+ assertEquals("Value(null)", nullElement.toString())
+ assertTrue(ValueOrClosed.value(null) == nullElement) // Don't box
+
+ expect(5)
+ val closed = channel.receiveOrClosed()
+ assertTrue(closed.isClosed)
+
+ val closed2 = channel.receiveOrClosed()
+ assertTrue(closed2.isClosed)
+ assertTrue(closed2.closeCause is ClosedReceiveChannelException)
+ finish(7)
+ }
+
+ @Test
+ @ExperimentalUnsignedTypes
+ fun testUIntChannel() = runTest {
+ val channel = Channel<UInt>()
+ launch {
+ expect(2)
+ channel.send(1u)
+ yield()
+ expect(4)
+ channel.send((Long.MAX_VALUE - 1).toUInt())
+ expect(5)
+ }
+
+ expect(1)
+ val element = channel.receiveOrClosed()
+ assertEquals(1u, element.value)
+
+ expect(3)
+ val element2 = channel.receiveOrClosed()
+ assertEquals((Long.MAX_VALUE - 1).toUInt(), element2.value)
+ finish(6)
+ }
+
+ @Test
+ fun testCancelChannel() = runTest {
+ val channel = Channel<Boolean>()
+ launch {
+ expect(2)
+ channel.cancel()
+ }
+
+ expect(1)
+ val closed = channel.receiveOrClosed()
+ assertTrue(closed.isClosed)
+ assertTrue(closed.closeCause is ClosedReceiveChannelException)
+ finish(3)
+ }
+
+ @Test
+ @ExperimentalUnsignedTypes
+ fun testReceiveResultChannel() = runTest {
+ val channel = Channel<ValueOrClosed<UInt>>()
+ launch {
+ channel.send(ValueOrClosed.value(1u))
+ channel.send(ValueOrClosed.closed(TestException1()))
+ channel.close(TestException2())
+ }
+
+ val intResult = channel.receiveOrClosed()
+ assertTrue(intResult.isValue)
+ assertEquals(1u, intResult.value.value)
+
+ val closeCauseResult = channel.receiveOrClosed()
+ assertTrue(closeCauseResult.isValue)
+ assertTrue(closeCauseResult.value.closeCause is TestException1)
+
+ val closeCause = channel.receiveOrClosed()
+ assertTrue(closeCause.isClosed)
+ assertTrue(closeCause.closeCause is TestException2)
+ assertFailsWith<TestException2> { closeCause.valueOrThrow }
+ }
+
+ @Test
+ fun testToString() = runTest {
+ val channel = Channel<String>(1)
+ channel.send("message")
+ channel.close(TestException1())
+ assertEquals("Value(message)", channel.receiveOrClosed().toString())
+ // toString implementation for exception differs on every platform
+ val str = channel.receiveOrClosed().toString()
+ assertTrue(str.matches("Closed\\(.*TestException1\\)".toRegex()))
+ }
+}
diff --git a/docs/basics.md b/docs/basics.md
index 642ea48..a7321bb 100644
--- a/docs/basics.md
+++ b/docs/basics.md
@@ -1,6 +1,6 @@
<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/docs/cancellation-and-timeouts.md b/docs/cancellation-and-timeouts.md
index 5f5fecd..afb8da9 100644
--- a/docs/cancellation-and-timeouts.md
+++ b/docs/cancellation-and-timeouts.md
@@ -1,6 +1,6 @@
<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/docs/channels.md b/docs/channels.md
index 2acb242..5550759 100644
--- a/docs/channels.md
+++ b/docs/channels.md
@@ -1,6 +1,6 @@
<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/docs/composing-suspending-functions.md b/docs/composing-suspending-functions.md
index 91cfeab..ed0d85c 100644
--- a/docs/composing-suspending-functions.md
+++ b/docs/composing-suspending-functions.md
@@ -1,6 +1,6 @@
<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/docs/coroutine-context-and-dispatchers.md b/docs/coroutine-context-and-dispatchers.md
index 8590ed6..cf2a9e4 100644
--- a/docs/coroutine-context-and-dispatchers.md
+++ b/docs/coroutine-context-and-dispatchers.md
@@ -1,6 +1,6 @@
<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/docs/exception-handling.md b/docs/exception-handling.md
index 5f15219..349b703 100644
--- a/docs/exception-handling.md
+++ b/docs/exception-handling.md
@@ -1,6 +1,6 @@
<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/docs/select-expression.md b/docs/select-expression.md
index b085b93..35480ab 100644
--- a/docs/select-expression.md
+++ b/docs/select-expression.md
@@ -1,6 +1,6 @@
<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
@@ -163,7 +163,7 @@
### Selecting on close
The [onReceive][ReceiveChannel.onReceive] clause in `select` fails when the channel is closed causing the corresponding
-`select` to throw an exception. We can use [onReceiveOrNull][ReceiveChannel.onReceiveOrNull] clause to perform a
+`select` to throw an exception. We can use [onReceiveOrNull][onReceiveOrNull] clause to perform a
specific action when the channel is closed. The following example also shows that `select` is an expression that returns
the result of its selected clause:
@@ -189,6 +189,10 @@
</div>
+Note that [onReceiveOrNull][onReceiveOrNull] is an extension function defined only
+for channels with non-nullable elements so that there is no accidental confusion between a closed channel
+and a null value.
+
Let's use it with channel `a` that produces "Hello" string four times and
channel `b` that produces "World" four times:
@@ -259,7 +263,7 @@
being the first clause in select, wins. However, because we are using unbuffered channel, the `a` gets suspended from
time to time on its [send][SendChannel.send] invocation and gives a chance for `b` to send, too.
-The second observation, is that [onReceiveOrNull][ReceiveChannel.onReceiveOrNull] gets immediately selected when the
+The second observation, is that [onReceiveOrNull][onReceiveOrNull] gets immediately selected when the
channel is already closed.
### Selecting to send
@@ -433,7 +437,7 @@
Let us write a channel producer function that consumes a channel of deferred string values, waits for each received
deferred value, but only until the next deferred value comes over or the channel is closed. This example puts together
-[onReceiveOrNull][ReceiveChannel.onReceiveOrNull] and [onAwait][Deferred.onAwait] clauses in the same `select`:
+[onReceiveOrNull][onReceiveOrNull] and [onAwait][Deferred.onAwait] clauses in the same `select`:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
@@ -556,7 +560,7 @@
<!--- INDEX kotlinx.coroutines.channels -->
[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
[ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
-[ReceiveChannel.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-or-null.html
+[onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/on-receive-or-null.html
[SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
[SendChannel.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/on-send.html
<!--- INDEX kotlinx.coroutines.selects -->
diff --git a/docs/shared-mutable-state-and-concurrency.md b/docs/shared-mutable-state-and-concurrency.md
index ed8f330..48d3d70 100644
--- a/docs/shared-mutable-state-and-concurrency.md
+++ b/docs/shared-mutable-state-and-concurrency.md
@@ -1,6 +1,6 @@
<!--- INCLUDE .*/example-([a-z]+)-([0-9a-z]+)\.kt
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/gradle.properties b/gradle.properties
index bf4a937..60c65a8 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,7 +1,7 @@
# Kotlin
-version=1.3.0-M2-SNAPSHOT
+version=1.3.0-RC-SNAPSHOT
group=org.jetbrains.kotlinx
-kotlin_version=1.3.40
+kotlin_version=1.3.41
# Dependencies
junit_version=4.12
@@ -14,7 +14,6 @@
reactor_vesion=3.2.5.RELEASE
reactive_streams_version=1.0.2
rxjava2_version=2.2.8
-artifactory_plugin_version=4.7.3
# JS
gradle_node_version=1.2.0
@@ -24,5 +23,6 @@
mocha_headless_chrome_version=1.8.2
mocha_teamcity_reporter_version=2.2.2
source_map_support_version=0.5.3
+spring_dependency_management_version=1.0.8.RELEASE
kotlin.incremental.multiplatform=true
diff --git a/gradle/publish-bintray.gradle b/gradle/publish-bintray.gradle
index edf07ef..0e37c67 100644
--- a/gradle/publish-bintray.gradle
+++ b/gradle/publish-bintray.gradle
@@ -7,7 +7,6 @@
apply plugin: 'maven'
apply plugin: 'maven-publish'
apply plugin: 'com.jfrog.bintray'
-apply plugin: 'com.jfrog.artifactory'
apply plugin: "com.github.johnrengelman.shadow"
apply from: project.rootProject.file('gradle/maven-central.gradle')
@@ -16,6 +15,8 @@
def bUser = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER')
def bKey = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY')
+def isMultiplatform = project.name == "kotlinx-coroutines-core"
+def isBom = project.name == "kotlinx-coroutines-bom"
task stubSources(type: Jar) {
classifier = 'sources'
@@ -32,7 +33,7 @@
classifier = 'sources'
if (project.name == "kotlinx-coroutines-core") {
from kotlin.sourceSets.commonMain.kotlin
- } else {
+ } else if (!isBom) {
from sourceSets.main.allSource
}
}
@@ -61,24 +62,30 @@
}
}
-def isMultiplatform = project.name == "kotlinx-coroutines-core"
-
publishing {
repositories {
maven { url = 'https://kotlin.bintray.com/kotlinx' }
}
- if (!isMultiplatform) {
+ if (isBom) {
+ publications {
+ mavenBom(MavenPublication) {
+ pom.withXml(configureMavenCentralMetadata)
+ }
+ }
+ return
+ } else if (!isMultiplatform) {
publications {
maven(MavenPublication) { publication ->
- if (project.name == "kotlinx-coroutines-debug") {
- project.shadow.component(publication)
- } else {
- publication.from components.java
- }
publication.artifact javadocJar
publication.artifact sourcesJar
publication.pom.withXml(configureMavenCentralMetadata)
+ if (project.name == "kotlinx-coroutines-debug") {
+ project.shadow.component(publication)
+ publication.pom.withXml(configureMavenDependencies)
+ } else {
+ publication.from components.java
+ }
}
}
@@ -144,27 +151,6 @@
}
}
-// snapshot publication is temporary disabled
-//artifactory {
-// contextUrl = 'https://oss.jfrog.org/artifactory'
-// publish {
-// repository {
-// repoKey = 'oss-snapshot-local'
-// username = bUser
-// password = bKey
-// }
-//
-// maven(MavenPublication) { publication ->
-// preparePublication(publication)
-// }
-//
-// defaults {
-// publications('maven')
-// }
-// }
-//}
-
-
task publishDevelopSnapshot() {
def branch = System.getenv('currentBranch')
if (branch == "develop") {
diff --git a/integration/kotlinx-coroutines-jdk8/src/time/Time.kt b/integration/kotlinx-coroutines-jdk8/src/time/Time.kt
index f0d9415..031ac61 100644
--- a/integration/kotlinx-coroutines-jdk8/src/time/Time.kt
+++ b/integration/kotlinx-coroutines-jdk8/src/time/Time.kt
@@ -46,7 +46,7 @@
* - Non-suspending fast-paths (e.g. `withTimeout(1 nanosecond) { 42 }` should not throw)
*/
private fun Duration.coerceToMillis(): Long {
- if (isNegative) return 0
+ if (this <= Duration.ZERO) return 0
if (this <= ChronoUnit.MILLIS.duration) return 1
// Maximum scalar values of Duration.ofMillis(Long.MAX_VALUE)
diff --git a/integration/kotlinx-coroutines-jdk8/test/time/DurationOverflowTest.kt b/integration/kotlinx-coroutines-jdk8/test/time/DurationOverflowTest.kt
index a9b2752..9ab0ccf 100644
--- a/integration/kotlinx-coroutines-jdk8/test/time/DurationOverflowTest.kt
+++ b/integration/kotlinx-coroutines-jdk8/test/time/DurationOverflowTest.kt
@@ -65,4 +65,15 @@
assertNull(result)
}
+ @Test
+ fun testZeroDurationWithTimeout() = runTest {
+ assertFailsWith<TimeoutCancellationException> { withTimeout(0L) {} }
+ assertFailsWith<TimeoutCancellationException> { withTimeout(Duration.ZERO) {} }
+ }
+
+ @Test
+ fun testZeroDurationWithTimeoutOrNull() = runTest {
+ assertNull(withTimeoutOrNull(0L) {})
+ assertNull(withTimeoutOrNull(Duration.ZERO) {})
+ }
}
diff --git a/knit/src/Knit.kt b/knit/src/Knit.kt
index c2e9fef..abb66df 100644
--- a/knit/src/Knit.kt
+++ b/knit/src/Knit.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
import java.io.*
@@ -228,7 +228,7 @@
}
}
for (code in codeLines) {
- outLines += code.replace("System.currentTimeMillis()", "timeSource.currentTimeMillis()")
+ outLines += code.replace("System.currentTimeMillis()", "currentTimeMillis()")
}
codeLines.clear()
writeLinesIfNeeded(file, outLines)
diff --git a/kotlinx-coroutines-bom/build.gradle b/kotlinx-coroutines-bom/build.gradle
new file mode 100644
index 0000000..9ec43b2
--- /dev/null
+++ b/kotlinx-coroutines-bom/build.gradle
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+plugins {
+ id 'io.spring.dependency-management'
+}
+
+def name = project.name
+
+dependencyManagement {
+ dependencies {
+ rootProject.subprojects.each {
+ if (!ext.unpublished.contains(it.name) && it.name != name) {
+ dependency(group: it.group, name: it.name, version: it.version)
+ }
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/README.md b/kotlinx-coroutines-core/README.md
index 2dc751e..5fe3298 100644
--- a/kotlinx-coroutines-core/README.md
+++ b/kotlinx-coroutines-core/README.md
@@ -56,7 +56,7 @@
| [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait] | [isCompleted][Job.isCompleted]
| [SendChannel][kotlinx.coroutines.channels.SendChannel] | [send][kotlinx.coroutines.channels.SendChannel.send] | [onSend][kotlinx.coroutines.channels.SendChannel.onSend] | [offer][kotlinx.coroutines.channels.SendChannel.offer]
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
-| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.channels.ReceiveChannel.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.channels.ReceiveChannel.onReceiveOrNull] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
+| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.channels.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.channels.onReceiveOrNull] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
| [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
@@ -131,8 +131,8 @@
[kotlinx.coroutines.channels.SendChannel.offer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/offer.html
[kotlinx.coroutines.channels.ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
[kotlinx.coroutines.channels.ReceiveChannel.poll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/poll.html
-[kotlinx.coroutines.channels.ReceiveChannel.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive-or-null.html
-[kotlinx.coroutines.channels.ReceiveChannel.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-or-null.html
+[kotlinx.coroutines.channels.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/receive-or-null.html
+[kotlinx.coroutines.channels.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/on-receive-or-null.html
<!--- INDEX kotlinx.coroutines.selects -->
[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/-select-builder/on-timeout.html
diff --git a/kotlinx-coroutines-core/common/README.md b/kotlinx-coroutines-core/common/README.md
index b84cedf..a0cc809 100644
--- a/kotlinx-coroutines-core/common/README.md
+++ b/kotlinx-coroutines-core/common/README.md
@@ -59,7 +59,7 @@
| [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait] | [isCompleted][Job.isCompleted]
| [SendChannel][kotlinx.coroutines.channels.SendChannel] | [send][kotlinx.coroutines.channels.SendChannel.send] | [onSend][kotlinx.coroutines.channels.SendChannel.onSend] | [offer][kotlinx.coroutines.channels.SendChannel.offer]
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
-| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.channels.ReceiveChannel.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.channels.ReceiveChannel.onReceiveOrNull] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
+| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.channels.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.channels.onReceiveOrNull] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
| [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
@@ -143,8 +143,8 @@
[kotlinx.coroutines.channels.SendChannel.offer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/offer.html
[kotlinx.coroutines.channels.ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
[kotlinx.coroutines.channels.ReceiveChannel.poll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/poll.html
-[kotlinx.coroutines.channels.ReceiveChannel.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive-or-null.html
-[kotlinx.coroutines.channels.ReceiveChannel.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-or-null.html
+[kotlinx.coroutines.channels.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/receive-or-null.html
+[kotlinx.coroutines.channels.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/on-receive-or-null.html
<!--- INDEX kotlinx.coroutines.selects -->
[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/-select-builder/on-timeout.html
diff --git a/kotlinx-coroutines-core/common/src/Annotations.kt b/kotlinx-coroutines-core/common/src/Annotations.kt
index 5ee89b8..742a7d7 100644
--- a/kotlinx-coroutines-core/common/src/Annotations.kt
+++ b/kotlinx-coroutines-core/common/src/Annotations.kt
@@ -52,4 +52,5 @@
*/
@Retention(value = AnnotationRetention.BINARY)
@Experimental(level = Experimental.Level.ERROR)
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPEALIAS, AnnotationTarget.PROPERTY)
public annotation class InternalCoroutinesApi
diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt
index a1a9097..40344c9 100644
--- a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt
+++ b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -297,7 +297,7 @@
}
is CompletedIdempotentResult -> {
return if (state.idempotentResume === idempotent) {
- check(state.result === value) { "Non-idempotent resume" }
+ assert { state.result === value } // "Non-idempotent resume"
state.token
} else {
null
diff --git a/kotlinx-coroutines-core/common/src/CoroutineScope.kt b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
index 92977b1..a9c7fb3 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineScope.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
@@ -203,6 +203,13 @@
}
/**
+ * Cancels this scope, including its job and all its children with a specified diagnostic error [message].
+ * A [cause] can be specified to provide additional details on a cancellation reason for debugging purposes.
+ * Throws [IllegalStateException] if the scope does not have a job in it.
+ */
+public fun CoroutineScope.cancel(message: String, cause: Throwable? = null): Unit = cancel(CancellationException(message, cause))
+
+/**
* Ensures that current scope is [active][CoroutineScope.isActive].
* Throws [IllegalStateException] if the context does not have a job in it.
*
diff --git a/kotlinx-coroutines-core/common/src/Debug.common.kt b/kotlinx-coroutines-core/common/src/Debug.common.kt
index 92dd552..dd09a6a 100644
--- a/kotlinx-coroutines-core/common/src/Debug.common.kt
+++ b/kotlinx-coroutines-core/common/src/Debug.common.kt
@@ -1,8 +1,10 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
+internal expect val DEBUG: Boolean
internal expect val Any.hexAddress: String
internal expect val Any.classSimpleName: String
+internal expect fun assert(value: () -> Boolean)
diff --git a/kotlinx-coroutines-core/common/src/Dispatched.kt b/kotlinx-coroutines-core/common/src/Dispatched.kt
index 450163c..a9624bd 100644
--- a/kotlinx-coroutines-core/common/src/Dispatched.kt
+++ b/kotlinx-coroutines-core/common/src/Dispatched.kt
@@ -87,7 +87,7 @@
override fun takeState(): Any? {
val state = _state
- check(state !== UNDEFINED) // fail-fast if repeatedly invoked
+ assert { state !== UNDEFINED } // fail-fast if repeatedly invoked
_state = UNDEFINED
return state
}
@@ -307,7 +307,14 @@
val state = takeState()
val exception = getExceptionalResult(state)
if (exception != null) {
- delegate.resumeWithExceptionMode(exception, useMode)
+ /*
+ * Recover stacktrace for non-dispatched tasks.
+ * We usually do not recover stacktrace in a `resume` as all resumes go through `DispatchedTask.run`
+ * and we recover stacktraces there, but this is not the case for a `suspend fun main()` that knows nothing about
+ * kotlinx.coroutines and DispatchedTask
+ */
+ val recovered = if (delegate is DispatchedTask<*>) exception else recoverStackTrace(exception, delegate)
+ delegate.resumeWithExceptionMode(recovered, useMode)
} else {
delegate.resumeMode(getSuccessfulResult(state), useMode)
}
diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt
index 3565292..7107059 100644
--- a/kotlinx-coroutines-core/common/src/EventLoop.common.kt
+++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt
@@ -1,10 +1,13 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
+import kotlinx.atomicfu.*
import kotlinx.coroutines.internal.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
/**
* Extended by [CoroutineDispatcher] implementations that have event loop inside and can
@@ -12,18 +15,18 @@
*
* It may optionally implement [Delay] interface and support time-scheduled tasks.
* It is created or pigged back onto (see [ThreadLocalEventLoop])
- * by [runBlocking] and by [Dispatchers.Unconfined].
+ * by `runBlocking` and by [Dispatchers.Unconfined].
*
* @suppress **This an internal API and should not be used from general code.**
*/
internal abstract class EventLoop : CoroutineDispatcher() {
/**
- * Counts the number of nested [runBlocking] and [Dispatchers.Unconfined] that use this event loop.
+ * Counts the number of nested `runBlocking` and [Dispatchers.Unconfined] that use this event loop.
*/
private var useCount = 0L
/**
- * Set to true on any use by [runBlocking], because it potentially leaks this loop to other threads, so
+ * Set to true on any use by `runBlocking`, because it potentially leaks this loop to other threads, so
* this instance must be properly shutdown. We don't need to shutdown event loop that was used solely
* by [Dispatchers.Unconfined] -- it can be left as [ThreadLocalEventLoop] and reused next time.
*/
@@ -104,7 +107,7 @@
fun decrementUseCount(unconfined: Boolean = false) {
useCount -= delta(unconfined)
if (useCount > 0) return
- check(useCount == 0L) { "Extra decrementUseCount" }
+ assert { useCount == 0L } // "Extra decrementUseCount"
if (shared) {
// shut it down and remove from ThreadLocalEventLoop
shutdown()
@@ -133,5 +136,387 @@
}
}
+@SharedImmutable
+private val DISPOSED_TASK = Symbol("REMOVED_TASK")
+
+// results for scheduleImpl
+private const val SCHEDULE_OK = 0
+private const val SCHEDULE_COMPLETED = 1
+private const val SCHEDULE_DISPOSED = 2
+
+private const val MS_TO_NS = 1_000_000L
+private const val MAX_MS = Long.MAX_VALUE / MS_TO_NS
+
+/**
+ * First-line overflow protection -- limit maximal delay.
+ * Delays longer than this one (~146 years) are considered to be delayed "forever".
+ */
+private const val MAX_DELAY_NS = Long.MAX_VALUE / 2
+
+internal fun delayToNanos(timeMillis: Long): Long = when {
+ timeMillis <= 0 -> 0L
+ timeMillis >= MAX_MS -> Long.MAX_VALUE
+ else -> timeMillis * MS_TO_NS
+}
+
+internal fun delayNanosToMillis(timeNanos: Long): Long =
+ timeNanos / MS_TO_NS
+
+@SharedImmutable
+private val CLOSED_EMPTY = Symbol("CLOSED_EMPTY")
+
+private typealias Queue<T> = LockFreeTaskQueueCore<T>
+
+internal expect abstract class EventLoopImplPlatform() : EventLoop {
+ // Called to unpark this event loop's thread
+ protected fun unpark()
+
+ // Called to reschedule to DefaultExecutor when this event loop is complete
+ protected fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask)
+}
+
+internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay {
+ // null | CLOSED_EMPTY | task | Queue<Runnable>
+ private val _queue = atomic<Any?>(null)
+
+ // Allocated only only once
+ private val _delayed = atomic<DelayedTaskQueue?>(null)
+
+ @Volatile
+ private var isCompleted = false
+
+ override val isEmpty: Boolean get() {
+ if (!isUnconfinedQueueEmpty) return false
+ val delayed = _delayed.value
+ if (delayed != null && !delayed.isEmpty) return false
+ val queue = _queue.value
+ return when (queue) {
+ null -> true
+ is Queue<*> -> queue.isEmpty
+ else -> queue === CLOSED_EMPTY
+ }
+ }
+
+ protected override val nextTime: Long
+ get() {
+ if (super.nextTime == 0L) return 0L
+ val queue = _queue.value
+ when {
+ queue === null -> {} // empty queue -- proceed
+ queue is Queue<*> -> if (!queue.isEmpty) return 0 // non-empty queue
+ queue === CLOSED_EMPTY -> return Long.MAX_VALUE // no more events -- closed
+ else -> return 0 // non-empty queue
+ }
+ val nextDelayedTask = _delayed.value?.peek() ?: return Long.MAX_VALUE
+ return (nextDelayedTask.nanoTime - nanoTime()).coerceAtLeast(0)
+ }
+
+ override fun shutdown() {
+ // Clean up thread-local reference here -- this event loop is shutting down
+ ThreadLocalEventLoop.resetEventLoop()
+ // We should signal that this event loop should not accept any more tasks
+ // and process queued events (that could have been added after last processNextEvent)
+ isCompleted = true
+ closeQueue()
+ // complete processing of all queued tasks
+ while (processNextEvent() <= 0) { /* spin */ }
+ // reschedule the rest of delayed tasks
+ rescheduleAllDelayed()
+ }
+
+ public override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
+ val timeNanos = delayToNanos(timeMillis)
+ if (timeNanos < MAX_DELAY_NS) {
+ val now = nanoTime()
+ DelayedResumeTask(now + timeNanos, continuation).also { task ->
+ continuation.disposeOnCancellation(task)
+ schedule(now, task)
+ }
+ }
+ }
+
+ protected fun scheduleInvokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
+ val timeNanos = delayToNanos(timeMillis)
+ return if (timeNanos < MAX_DELAY_NS) {
+ val now = nanoTime()
+ DelayedRunnableTask(now + timeNanos, block).also { task ->
+ schedule(now, task)
+ }
+ } else {
+ NonDisposableHandle
+ }
+ }
+
+ override fun processNextEvent(): Long {
+ // unconfined events take priority
+ if (processUnconfinedEvent()) return nextTime
+ // queue all delayed tasks that are due to be executed
+ val delayed = _delayed.value
+ if (delayed != null && !delayed.isEmpty) {
+ val now = nanoTime()
+ while (true) {
+ // make sure that moving from delayed to queue removes from delayed only after it is added to queue
+ // to make sure that 'isEmpty' and `nextTime` that check both of them
+ // do not transiently report that both delayed and queue are empty during move
+ delayed.removeFirstIf {
+ if (it.timeToExecute(now)) {
+ enqueueImpl(it)
+ } else
+ false
+ } ?: break // quit loop when nothing more to remove or enqueueImpl returns false on "isComplete"
+ }
+ }
+ // then process one event from queue
+ dequeue()?.run()
+ return nextTime
+ }
+
+ public final override fun dispatch(context: CoroutineContext, block: Runnable) = enqueue(block)
+
+ public fun enqueue(task: Runnable) {
+ if (enqueueImpl(task)) {
+ // todo: we should unpark only when this delayed task became first in the queue
+ unpark()
+ } else {
+ DefaultExecutor.enqueue(task)
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun enqueueImpl(task: Runnable): Boolean {
+ _queue.loop { queue ->
+ if (isCompleted) return false // fail fast if already completed, may still add, but queues will close
+ when (queue) {
+ null -> if (_queue.compareAndSet(null, task)) return true
+ is Queue<*> -> {
+ when ((queue as Queue<Runnable>).addLast(task)) {
+ Queue.ADD_SUCCESS -> return true
+ Queue.ADD_CLOSED -> return false
+ Queue.ADD_FROZEN -> _queue.compareAndSet(queue, queue.next())
+ }
+ }
+ else -> when {
+ queue === CLOSED_EMPTY -> return false
+ else -> {
+ // update to full-blown queue to add one more
+ val newQueue = Queue<Runnable>(Queue.INITIAL_CAPACITY, singleConsumer = true)
+ newQueue.addLast(queue as Runnable)
+ newQueue.addLast(task)
+ if (_queue.compareAndSet(queue, newQueue)) return true
+ }
+ }
+ }
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun dequeue(): Runnable? {
+ _queue.loop { queue ->
+ when (queue) {
+ null -> return null
+ is Queue<*> -> {
+ val result = (queue as Queue<Runnable>).removeFirstOrNull()
+ if (result !== Queue.REMOVE_FROZEN) return result as Runnable?
+ _queue.compareAndSet(queue, queue.next())
+ }
+ else -> when {
+ queue === CLOSED_EMPTY -> return null
+ else -> if (_queue.compareAndSet(queue, null)) return queue as Runnable
+ }
+ }
+ }
+ }
+
+ private fun closeQueue() {
+ assert { isCompleted }
+ _queue.loop { queue ->
+ when (queue) {
+ null -> if (_queue.compareAndSet(null, CLOSED_EMPTY)) return
+ is Queue<*> -> {
+ queue.close()
+ return
+ }
+ else -> when {
+ queue === CLOSED_EMPTY -> return
+ else -> {
+ // update to full-blown queue to close
+ val newQueue = Queue<Runnable>(Queue.INITIAL_CAPACITY, singleConsumer = true)
+ newQueue.addLast(queue as Runnable)
+ if (_queue.compareAndSet(queue, newQueue)) return
+ }
+ }
+ }
+ }
+
+ }
+
+ public fun schedule(now: Long, delayedTask: DelayedTask) {
+ when (scheduleImpl(now, delayedTask)) {
+ SCHEDULE_OK -> if (shouldUnpark(delayedTask)) unpark()
+ SCHEDULE_COMPLETED -> reschedule(now, delayedTask)
+ SCHEDULE_DISPOSED -> {} // do nothing -- task was already disposed
+ else -> error("unexpected result")
+ }
+ }
+
+ private fun shouldUnpark(task: DelayedTask): Boolean = _delayed.value?.peek() === task
+
+ private fun scheduleImpl(now: Long, delayedTask: DelayedTask): Int {
+ if (isCompleted) return SCHEDULE_COMPLETED
+ val delayedQueue = _delayed.value ?: run {
+ _delayed.compareAndSet(null, DelayedTaskQueue(now))
+ _delayed.value!!
+ }
+ return delayedTask.scheduleTask(now, delayedQueue, this)
+ }
+
+ // It performs "hard" shutdown for test cleanup purposes
+ protected fun resetAll() {
+ _queue.value = null
+ _delayed.value = null
+ }
+
+ // This is a "soft" (normal) shutdown
+ private fun rescheduleAllDelayed() {
+ val now = nanoTime()
+ while (true) {
+ /*
+ * `removeFirstOrNull` below is the only operation on DelayedTask & ThreadSafeHeap that is not
+ * synchronized on DelayedTask itself. All other operation are synchronized both on
+ * DelayedTask & ThreadSafeHeap instances (in this order). It is still safe, because `dispose`
+ * first removes DelayedTask from the heap (under synchronization) then
+ * assign "_heap = DISPOSED_TASK", so there cannot be ever a race to _heap reference update.
+ */
+ val delayedTask = _delayed.value?.removeFirstOrNull() ?: break
+ reschedule(now, delayedTask)
+ }
+ }
+
+ internal abstract class DelayedTask(
+ /**
+ * This field can be only modified in [scheduleTask] before putting this DelayedTask
+ * into heap to avoid overflow and corruption of heap data structure.
+ */
+ @JvmField var nanoTime: Long
+ ) : Runnable, Comparable<DelayedTask>, DisposableHandle, ThreadSafeHeapNode {
+ private var _heap: Any? = null // null | ThreadSafeHeap | DISPOSED_TASK
+
+ override var heap: ThreadSafeHeap<*>?
+ get() = _heap as? ThreadSafeHeap<*>
+ set(value) {
+ require(_heap !== DISPOSED_TASK) // this can never happen, it is always checked before adding/removing
+ _heap = value
+ }
+
+ override var index: Int = -1
+
+ override fun compareTo(other: DelayedTask): Int {
+ val dTime = nanoTime - other.nanoTime
+ return when {
+ dTime > 0 -> 1
+ dTime < 0 -> -1
+ else -> 0
+ }
+ }
+
+ fun timeToExecute(now: Long): Boolean = now - nanoTime >= 0L
+
+ @Synchronized
+ fun scheduleTask(now: Long, delayed: DelayedTaskQueue, eventLoop: EventLoopImplBase): Int {
+ if (_heap === DISPOSED_TASK) return SCHEDULE_DISPOSED // don't add -- was already disposed
+ delayed.addLastIf(this) { firstTask ->
+ if (eventLoop.isCompleted) return SCHEDULE_COMPLETED // non-local return from scheduleTask
+ /**
+ * We are about to add new task and we have to make sure that [DelayedTaskQueue]
+ * invariant is maintained. The code in this lambda is additionally executed under
+ * the lock of [DelayedTaskQueue] and working with [DelayedTaskQueue.timeNow] here is thread-safe.
+ */
+ if (firstTask == null) {
+ /**
+ * When adding the first delayed task we simply update queue's [DelayedTaskQueue.timeNow] to
+ * the current now time even if that means "going backwards in time". This makes the structure
+ * self-correcting in spite of wild jumps in `nanoTime()` measurements once all delayed tasks
+ * are removed from the delayed queue for execution.
+ */
+ delayed.timeNow = now
+ } else {
+ /**
+ * Carefully update [DelayedTaskQueue.timeNow] so that it does not sweep past first's tasks time
+ * and only goes forward in time. We cannot let it go backwards in time or invariant can be
+ * violated for tasks that were already scheduled.
+ */
+ val firstTime = firstTask.nanoTime
+ // compute min(now, firstTime) using a wrap-safe check
+ val minTime = if (firstTime - now >= 0) now else firstTime
+ // update timeNow only when going forward in time
+ if (minTime - delayed.timeNow > 0) delayed.timeNow = minTime
+ }
+ /**
+ * Here [DelayedTaskQueue.timeNow] was already modified and we have to double-check that newly added
+ * task does not violate [DelayedTaskQueue] invariant because of that. Note also that this scheduleTask
+ * function can be called to reschedule from one queue to another and this might be another reason
+ * where new task's time might now violate invariant.
+ * We correct invariant violation (if any) by simply changing this task's time to now.
+ */
+ if (nanoTime - delayed.timeNow < 0) nanoTime = delayed.timeNow
+ true
+ }
+ return SCHEDULE_OK
+ }
+
+ @Synchronized
+ final override fun dispose() {
+ val heap = _heap
+ if (heap === DISPOSED_TASK) return // already disposed
+ @Suppress("UNCHECKED_CAST")
+ (heap as? DelayedTaskQueue)?.remove(this) // remove if it is in heap (first)
+ _heap = DISPOSED_TASK // never add again to any heap
+ }
+
+ override fun toString(): String = "Delayed[nanos=$nanoTime]"
+ }
+
+ private inner class DelayedResumeTask(
+ nanoTime: Long,
+ private val cont: CancellableContinuation<Unit>
+ ) : DelayedTask(nanoTime) {
+ override fun run() { with(cont) { resumeUndispatched(Unit) } }
+ override fun toString(): String = super.toString() + cont.toString()
+ }
+
+ private class DelayedRunnableTask(
+ nanoTime: Long,
+ private val block: Runnable
+ ) : DelayedTask(nanoTime) {
+ override fun run() { block.run() }
+ override fun toString(): String = super.toString() + block.toString()
+ }
+
+ /**
+ * Delayed task queue maintains stable time-comparision invariant despite potential wraparounds in
+ * long nano time measurements by maintaining last observed [timeNow]. It protects the integrity of the
+ * heap data structure in spite of potential non-monotonicity of `nanoTime()` source.
+ * The invariant is that for every scheduled [DelayedTask]:
+ *
+ * ```
+ * delayedTask.nanoTime - timeNow >= 0
+ * ```
+ *
+ * So the comparison of scheduled tasks via [DelayedTask.compareTo] is always stable as
+ * scheduled [DelayedTask.nanoTime] can be at most [Long.MAX_VALUE] apart. This invariant is maintained when
+ * new tasks are added by [DelayedTask.scheduleTask] function and it cannot be violated when tasks are removed
+ * (so there is nothing special to do there).
+ */
+ internal class DelayedTaskQueue(
+ @JvmField var timeNow: Long
+ ) : ThreadSafeHeap<DelayedTask>()
+}
+
internal expect fun createEventLoop(): EventLoop
+internal expect fun nanoTime(): Long
+
+internal expect object DefaultExecutor {
+ public fun enqueue(task: Runnable)
+}
+
diff --git a/kotlinx-coroutines-core/common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt
index 29232f7..c6716bc 100644
--- a/kotlinx-coroutines-core/common/src/Job.kt
+++ b/kotlinx-coroutines-core/common/src/Job.kt
@@ -577,6 +577,12 @@
}
/**
+ * Cancels current job, including all its children with a specified diagnostic error [message].
+ * A [cause] can be specified to provide additional details on a cancellation reason for debugging purposes.
+ */
+public fun Job.cancel(message: String, cause: Throwable? = null): Unit = cancel(CancellationException(message, cause))
+
+/**
* @suppress This method has bad semantics when cause is not a [CancellationException]. Use [CoroutineContext.cancel].
*/
@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
diff --git a/kotlinx-coroutines-core/common/src/JobSupport.kt b/kotlinx-coroutines-core/common/src/JobSupport.kt
index d8b6b92..63e34fd 100644
--- a/kotlinx-coroutines-core/common/src/JobSupport.kt
+++ b/kotlinx-coroutines-core/common/src/JobSupport.kt
@@ -136,10 +136,9 @@
/**
* Initializes parent job.
* It shall be invoked at most once after construction after all other initialization.
- * @suppress **This is unstable API and it is subject to change.**
*/
internal fun initParentJobInternal(parent: Job?) {
- check(parentHandle == null)
+ assert { parentHandle == null }
if (parent == null) {
parentHandle = NonDisposableHandle
return
@@ -269,8 +268,8 @@
// fast-path method to finalize normally completed coroutines without children
private fun tryFinalizeSimpleState(state: Incomplete, update: Any?, mode: Int): Boolean {
- check(state is Empty || state is JobNode<*>) // only simple state without lists where children can concurrently add
- check(update !is CompletedExceptionally) // only for normal completion
+ assert { state is Empty || state is JobNode<*> } // only simple state without lists where children can concurrently add
+ assert { update !is CompletedExceptionally } // only for normal completion
if (!_state.compareAndSet(state, update.boxIncomplete())) return false
onCancelling(null) // simple state is not a failure
onCompletionInternal(update)
@@ -397,16 +396,14 @@
*/
internal open fun onStartInternal() {}
- public final override fun getCancellationException(): CancellationException {
- val state = this.state
- return when (state) {
+ public final override fun getCancellationException(): CancellationException =
+ when (val state = this.state) {
is Finishing -> state.rootCause?.toCancellationException("$classSimpleName is cancelling")
?: error("Job is still new or active: $this")
is Incomplete -> error("Job is still new or active: $this")
is CompletedExceptionally -> state.cause.toCancellationException()
else -> JobCancellationException("$classSimpleName has completed normally", null, this)
}
- }
protected fun Throwable.toCancellationException(message: String? = null): CancellationException =
this as? CancellationException ?:
@@ -747,8 +744,8 @@
// try make new Cancelling state on the condition that we're still in the expected state
private fun tryMakeCancelling(state: Incomplete, rootCause: Throwable): Boolean {
- check(state !is Finishing) // only for non-finishing states
- check(state.isActive) // only for active states
+ assert { state !is Finishing } // only for non-finishing states
+ assert { state.isActive } // only for active states
// get state's list or else promote to list to correctly operate on child lists
val list = getOrPromoteCancellingList(state) ?: return false
// Create cancelling state (with rootCause!)
@@ -1037,8 +1034,7 @@
// Seals current state and returns list of exceptions
// guarded by `synchronized(this)`
fun sealLocked(proposedException: Throwable?): List<Throwable> {
- val eh = _exceptionsHolder // volatile read
- val list = when(eh) {
+ val list = when(val eh = _exceptionsHolder) { // volatile read
null -> allocateList()
is Throwable -> allocateList().also { it.add(eh) }
is ArrayList<*> -> eh as ArrayList<Throwable>
@@ -1305,14 +1301,15 @@
append("]")
}
- override fun toString(): String = getString("Active")
+ override fun toString(): String =
+ if (DEBUG) getString("Active") else super.toString()
}
internal class InactiveNodeList(
override val list: NodeList
) : Incomplete {
override val isActive: Boolean get() = false
- override fun toString(): String = list.getString("New")
+ override fun toString(): String = if (DEBUG) list.getString("New") else super.toString()
}
private class InvokeOnCompletion(
@@ -1337,7 +1334,7 @@
) : JobNode<JobSupport>(job) {
override fun invoke(cause: Throwable?) {
val state = job.state
- check(state !is Incomplete)
+ assert { state !is Incomplete }
if (state is CompletedExceptionally) {
// Resume with exception in atomic way to preserve exception
continuation.resumeWithExceptionMode(state.cause, MODE_ATOMIC_DEFAULT)
diff --git a/kotlinx-coroutines-core/common/src/NonCancellable.kt b/kotlinx-coroutines-core/common/src/NonCancellable.kt
index 3a4faee..c48faea 100644
--- a/kotlinx-coroutines-core/common/src/NonCancellable.kt
+++ b/kotlinx-coroutines-core/common/src/NonCancellable.kt
@@ -115,4 +115,9 @@
*/
@InternalCoroutinesApi
override fun attachChild(child: ChildJob): ChildHandle = NonDisposableHandle
+
+ /** @suppress */
+ override fun toString(): String {
+ return "NonCancellable"
+ }
}
diff --git a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
index 61bc090..bed4979 100644
--- a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -14,7 +14,6 @@
/**
* Abstract send channel. It is a base class for all send channel implementations.
- *
*/
internal abstract class AbstractSendChannel<E> : SendChannel<E> {
/** @suppress **This is unstable API and it is subject to change.** */
@@ -449,7 +448,7 @@
if (select.trySelect(idempotent)) SELECT_STARTED else null
override fun completeResumeSend(token: Any) {
- check(token === SELECT_STARTED)
+ assert { token === SELECT_STARTED }
block.startCoroutine(receiver = channel, completion = select.completion)
}
@@ -474,7 +473,7 @@
) : LockFreeLinkedListNode(), Send {
override val pollResult: Any? get() = element
override fun tryResumeSend(idempotent: Any?): Any? = SEND_RESUMED
- override fun completeResumeSend(token: Any) { check(token === SEND_RESUMED) }
+ override fun completeResumeSend(token: Any) { assert { token === SEND_RESUMED } }
override fun resumeSendClosed(closed: Closed<*>) {}
}
}
@@ -542,13 +541,12 @@
public final override val isClosedForReceive: Boolean get() = closedForReceive != null && isBufferEmpty
public final override val isEmpty: Boolean get() = queue.nextNode !is Send && isBufferEmpty
- @Suppress("UNCHECKED_CAST")
public final override suspend fun receive(): E {
// fast path -- try poll non-blocking
val result = pollInternal()
if (result !== POLL_FAILED) return receiveResult(result)
// slow-path does suspend
- return receiveSuspend()
+ return receiveSuspend(RECEIVE_THROWS_ON_CLOSE)
}
@Suppress("UNCHECKED_CAST")
@@ -558,8 +556,8 @@
}
@Suppress("UNCHECKED_CAST")
- private suspend fun receiveSuspend(): E = suspendAtomicCancellableCoroutine sc@ { cont ->
- val receive = ReceiveElement(cont as CancellableContinuation<E?>, nullOnClose = false)
+ private suspend fun <R> receiveSuspend(onClose: Int): R = suspendAtomicCancellableCoroutine sc@ { cont ->
+ val receive = ReceiveElement<E>(cont as CancellableContinuation<Any?>, onClose)
while (true) {
if (enqueueReceive(receive)) {
removeReceiveOnCancel(cont, receive)
@@ -568,11 +566,11 @@
// hm... something is not right. try to poll
val result = pollInternal()
if (result is Closed<*>) {
- cont.resumeWithException(result.receiveException)
+ receive.resumeReceiveClosed(result)
return@sc
}
if (result !== POLL_FAILED) {
- cont.resume(result as E)
+ cont.resume(receive.resumeValue(result as E))
return@sc
}
}
@@ -586,13 +584,12 @@
return result
}
- @Suppress("UNCHECKED_CAST")
public final override suspend fun receiveOrNull(): E? {
// fast path -- try poll non-blocking
val result = pollInternal()
if (result !== POLL_FAILED) return receiveOrNullResult(result)
// slow-path does suspend
- return receiveOrNullSuspend()
+ return receiveSuspend(RECEIVE_NULL_ON_CLOSE)
}
@Suppress("UNCHECKED_CAST")
@@ -605,27 +602,12 @@
}
@Suppress("UNCHECKED_CAST")
- private suspend fun receiveOrNullSuspend(): E? = suspendAtomicCancellableCoroutine sc@ { cont ->
- val receive = ReceiveElement(cont, nullOnClose = true)
- while (true) {
- if (enqueueReceive(receive)) {
- removeReceiveOnCancel(cont, receive)
- return@sc
- }
- // hm... something is not right. try to poll
- val result = pollInternal()
- if (result is Closed<*>) {
- if (result.closeCause == null)
- cont.resume(null)
- else
- cont.resumeWithException(result.closeCause)
- return@sc
- }
- if (result !== POLL_FAILED) {
- cont.resume(result as E)
- return@sc
- }
- }
+ public final override suspend fun receiveOrClosed(): ValueOrClosed<E> {
+ // fast path -- try poll non-blocking
+ val result = pollInternal()
+ if (result !== POLL_FAILED) return result.toResult()
+ // slow-path does suspend
+ return receiveSuspend(RECEIVE_RESULT)
}
@Suppress("UNCHECKED_CAST")
@@ -654,7 +636,7 @@
while (true) {
val send = takeFirstSendOrPeekClosed() ?: error("Cannot happen")
if (send is Closed<*>) {
- check(send === closed)
+ assert { send === closed }
return // cleaned
}
send.resumeSendClosed(closed)
@@ -694,9 +676,9 @@
private inner class TryEnqueueReceiveDesc<E, R>(
select: SelectInstance<R>,
- block: suspend (E?) -> R,
- nullOnClose: Boolean
- ) : AddLastDesc<ReceiveSelect<R, E>>(queue, ReceiveSelect(select, block, nullOnClose)) {
+ block: suspend (Any?) -> R,
+ receiveMode: Int
+ ) : AddLastDesc<ReceiveSelect<R, E>>(queue, ReceiveSelect(select, block, receiveMode)) {
override fun failure(affected: LockFreeLinkedListNode, next: Any): Any? {
if (affected is Send) return ENQUEUE_FAILED
return null
@@ -728,13 +710,7 @@
while (true) {
if (select.isSelected) return
if (isEmpty) {
- val enqueueOp = TryEnqueueReceiveDesc(select, block as (suspend (E?) -> R), nullOnClose = false)
- val enqueueResult = select.performAtomicIfNotSelected(enqueueOp) ?: return
- when {
- enqueueResult === ALREADY_SELECTED -> return
- enqueueResult === ENQUEUE_FAILED -> {} // retry
- else -> error("performAtomicIfNotSelected(TryEnqueueReceiveDesc) returned $enqueueResult")
- }
+ if (registerEnqueueDesc(select, block, RECEIVE_THROWS_ON_CLOSE)) return
} else {
val pollResult = pollSelectInternal(select)
when {
@@ -762,13 +738,7 @@
while (true) {
if (select.isSelected) return
if (isEmpty) {
- val enqueueOp = TryEnqueueReceiveDesc(select, block, nullOnClose = true)
- val enqueueResult = select.performAtomicIfNotSelected(enqueueOp) ?: return
- when {
- enqueueResult === ALREADY_SELECTED -> return
- enqueueResult === ENQUEUE_FAILED -> {} // retry
- else -> error("performAtomicIfNotSelected(TryEnqueueReceiveDesc) returned $enqueueResult")
- }
+ if (registerEnqueueDesc(select, block, RECEIVE_NULL_ON_CLOSE)) return
} else {
val pollResult = pollSelectInternal(select)
when {
@@ -793,6 +763,51 @@
}
}
+ override val onReceiveOrClosed: SelectClause1<ValueOrClosed<E>>
+ get() = object : SelectClause1<ValueOrClosed<E>> {
+ override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (ValueOrClosed<E>) -> R) {
+ registerSelectReceiveOrClosed(select, block)
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun <R> registerSelectReceiveOrClosed(select: SelectInstance<R>, block: suspend (ValueOrClosed<E>) -> R) {
+ while (true) {
+ if (select.isSelected) return
+ if (isEmpty) {
+ if (registerEnqueueDesc(select, block, RECEIVE_RESULT)) return
+ } else {
+ val pollResult = pollSelectInternal(select)
+ when {
+ pollResult === ALREADY_SELECTED -> return
+ pollResult === POLL_FAILED -> {} // retry
+ pollResult is Closed<*> -> {
+ block.startCoroutineUnintercepted(ValueOrClosed.closed(pollResult.closeCause), select.completion)
+ }
+ else -> {
+ // selected successfully
+ block.startCoroutineUnintercepted(ValueOrClosed.value(pollResult as E), select.completion)
+ return
+ }
+ }
+ }
+ }
+ }
+
+ private fun <R, E> registerEnqueueDesc(
+ select: SelectInstance<R>, block: suspend (E) -> R,
+ receiveMode: Int
+ ): Boolean {
+ @Suppress("UNCHECKED_CAST")
+ val enqueueOp = TryEnqueueReceiveDesc<E, R>(select, block as suspend (Any?) -> R, receiveMode)
+ val enqueueResult = select.performAtomicIfNotSelected(enqueueOp) ?: return true
+ return when {
+ enqueueResult === ALREADY_SELECTED -> true
+ enqueueResult === ENQUEUE_FAILED -> false // retry
+ else -> error("performAtomicIfNotSelected(TryEnqueueReceiveDesc) returned $enqueueResult")
+ }
+ }
+
// ------ protected ------
override fun takeFirstReceiveOrPeekClosed(): ReceiveOrClosed<E>? =
@@ -884,18 +899,25 @@
}
private class ReceiveElement<in E>(
- @JvmField val cont: CancellableContinuation<E?>,
- @JvmField val nullOnClose: Boolean
+ @JvmField val cont: CancellableContinuation<Any?>,
+ @JvmField val receiveMode: Int
) : Receive<E>() {
- override fun tryResumeReceive(value: E, idempotent: Any?): Any? = cont.tryResume(value, idempotent)
+ fun resumeValue(value: E): Any? = when (receiveMode) {
+ RECEIVE_RESULT -> ValueOrClosed.value(value)
+ else -> value
+ }
+
+ @Suppress("IMPLICIT_CAST_TO_ANY")
+ override fun tryResumeReceive(value: E, idempotent: Any?): Any? = cont.tryResume(resumeValue(value), idempotent)
override fun completeResumeReceive(token: Any) = cont.completeResume(token)
override fun resumeReceiveClosed(closed: Closed<*>) {
- if (closed.closeCause == null && nullOnClose)
- cont.resume(null)
- else
- cont.resumeWithException(closed.receiveException)
+ when {
+ receiveMode == RECEIVE_NULL_ON_CLOSE && closed.closeCause == null -> cont.resume(null)
+ receiveMode == RECEIVE_RESULT -> cont.resume(closed.toResult<Any>())
+ else -> cont.resumeWithException(closed.receiveException)
+ }
}
- override fun toString(): String = "ReceiveElement[$cont,nullOnClose=$nullOnClose]"
+ override fun toString(): String = "ReceiveElement[$cont,receiveMode=$receiveMode]"
}
private class ReceiveHasNext<E>(
@@ -940,25 +962,26 @@
private inner class ReceiveSelect<R, in E>(
@JvmField val select: SelectInstance<R>,
- @JvmField val block: suspend (E?) -> R,
- @JvmField val nullOnClose: Boolean
+ @JvmField val block: suspend (Any?) -> R,
+ @JvmField val receiveMode: Int
) : Receive<E>(), DisposableHandle {
override fun tryResumeReceive(value: E, idempotent: Any?): Any? =
if (select.trySelect(idempotent)) (value ?: NULL_VALUE) else null
@Suppress("UNCHECKED_CAST")
override fun completeResumeReceive(token: Any) {
- block.startCoroutine(NULL_VALUE.unbox<E>(token), select.completion)
+ val value: E = NULL_VALUE.unbox<E>(token)
+ block.startCoroutine(if (receiveMode == RECEIVE_RESULT) ValueOrClosed.value(value) else value, select.completion)
}
override fun resumeReceiveClosed(closed: Closed<*>) {
- if (select.trySelect(null)) {
- if (closed.closeCause == null && nullOnClose) {
+ if (!select.trySelect(null)) return
+ when (receiveMode) {
+ RECEIVE_THROWS_ON_CLOSE -> select.resumeSelectCancellableWithException(closed.receiveException)
+ RECEIVE_RESULT -> block.startCoroutine(ValueOrClosed.closed<R>(closed.closeCause), select.completion)
+ RECEIVE_NULL_ON_CLOSE -> if (closed.closeCause == null) {
block.startCoroutine(null, select.completion)
} else {
- // even though we are dispatching coroutine to process channel close on receive,
- // which is an atomically cancellable suspending function,
- // close is a final state, so we can use a cancellable resume mode
select.resumeSelectCancellableWithException(closed.receiveException)
}
}
@@ -973,7 +996,7 @@
onReceiveDequeued() // notify cancellation of receive
}
- override fun toString(): String = "ReceiveSelect[$select,nullOnClose=$nullOnClose]"
+ override fun toString(): String = "ReceiveSelect[$select,receiveMode=$receiveMode]"
}
private class IdempotentTokenValue<out E>(
@@ -982,6 +1005,11 @@
)
}
+// receiveMode values
+internal const val RECEIVE_THROWS_ON_CLOSE = 0
+internal const val RECEIVE_NULL_ON_CLOSE = 1
+internal const val RECEIVE_RESULT = 2
+
@JvmField
@SharedImmutable
internal val OFFER_SUCCESS: Any = Symbol("OFFER_SUCCESS")
@@ -1065,10 +1093,10 @@
override val offerResult get() = this
override val pollResult get() = this
override fun tryResumeSend(idempotent: Any?): Any? = CLOSE_RESUMED
- override fun completeResumeSend(token: Any) { check(token === CLOSE_RESUMED) }
+ override fun completeResumeSend(token: Any) { assert { token === CLOSE_RESUMED } }
override fun tryResumeReceive(value: E, idempotent: Any?): Any? = CLOSE_RESUMED
- override fun completeResumeReceive(token: Any) { check(token === CLOSE_RESUMED) }
- override fun resumeSendClosed(closed: Closed<*>) = error("Should be never invoked")
+ override fun completeResumeReceive(token: Any) { assert { token === CLOSE_RESUMED } }
+ override fun resumeSendClosed(closed: Closed<*>) = assert { false } // "Should be never invoked"
override fun toString(): String = "Closed[$closeCause]"
}
@@ -1076,3 +1104,10 @@
override val offerResult get() = OFFER_SUCCESS
abstract fun resumeReceiveClosed(closed: Closed<*>)
}
+
+@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST")
+private inline fun <E> Any?.toResult(): ValueOrClosed<E> =
+ if (this is Closed<*>) ValueOrClosed.closed(closeCause) else ValueOrClosed.value(this as E)
+
+@Suppress("NOTHING_TO_INLINE")
+private inline fun <E> Closed<*>.toResult(): ValueOrClosed<E> = ValueOrClosed.closed(closeCause)
diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt
index 01afc21..688125d 100644
--- a/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt
@@ -1,9 +1,10 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
+import kotlinx.coroutines.*
import kotlinx.coroutines.internal.*
import kotlinx.coroutines.selects.*
import kotlin.jvm.*
@@ -94,7 +95,7 @@
this.size = size // restore size
receive = offerOp.result
token = offerOp.resumeToken
- check(token != null)
+ assert { token != null }
return@withLock
}
failure === OFFER_FAILED -> break@loop // cannot offer -> Ok to queue to buffer
@@ -180,7 +181,7 @@
failure == null -> { // polled successfully
send = pollOp.result
token = pollOp.resumeToken
- check(token != null)
+ assert { token != null }
replacement = send!!.pollResult
break@loop
}
diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt
index 9fa3418..a2a6da2 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt
@@ -15,6 +15,7 @@
import kotlinx.coroutines.internal.systemProp
import kotlinx.coroutines.selects.*
import kotlin.jvm.*
+import kotlin.internal.*
/**
* Sender's interface to [Channel].
@@ -91,7 +92,8 @@
* on the side of [ReceiveChannel] starts returning `true` only after all previously sent elements
* are received.
*
- * A channel that was closed without a [cause] throws [ClosedSendChannelException] on attempts to send or receive.
+ * A channel that was closed without a [cause] throws [ClosedSendChannelException] on attempts to send
+ * and [ClosedReceiveChannelException] on attempts to receive.
* A channel that was closed with non-null [cause] is called a _failed_ channel. Attempts to send or
* receive on a failed channel throw the specified [cause] exception.
*/
@@ -209,26 +211,74 @@
* This function can be used in [select] invocation with [onReceiveOrNull] clause.
* Use [poll] to try receiving from this channel without waiting.
*
- * **Note: This is an obsolete api.**
- * This function will be replaced with `receiveOrClosed: ReceiveResult<E>` and
- * extension `suspend fun <E: Any> ReceiveChannel<E>.receiveOrNull(): E?`
- * It is obsolete because it does not distinguish closed channel and null elements.
+ * @suppress **Deprecated**: in favor of receiveOrClosed and receiveOrNull extension.
*/
@ObsoleteCoroutinesApi
+ @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+ @LowPriorityInOverloadResolution
+ @Deprecated(
+ message = "Deprecated in favor of receiveOrClosed and receiveOrNull extension",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("receiveOrNull", "kotlinx.coroutines.channels.receiveOrNull")
+ )
public suspend fun receiveOrNull(): E?
/**
* Clause for [select] expression of [receiveOrNull] suspending function that selects with the element that
- * is received from the channel or selects with `null` if if the channel
+ * is received from the channel or selects with `null` if the channel
* [isClosedForReceive] without cause. The [select] invocation fails with
* the original [close][SendChannel.close] cause exception if the channel has _failed_.
*
- * **Note: This is an experimental api.** This function may be replaced with a better one in the future.
+ * @suppress **Deprecated**: in favor of onReceiveOrClosed and onReceiveOrNull extension.
*/
- @ExperimentalCoroutinesApi
+ @ObsoleteCoroutinesApi
+ @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+ @LowPriorityInOverloadResolution
+ @Deprecated(
+ message = "Deprecated in favor of onReceiveOrClosed and onReceiveOrNull extension",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("onReceiveOrNull", "kotlinx.coroutines.channels.onReceiveOrNull")
+ )
public val onReceiveOrNull: SelectClause1<E?>
/**
+ * Retrieves and removes the element from this channel suspending the caller while this channel [isEmpty].
+ * This method returns [ValueOrClosed] with a value if element was successfully retrieved from the channel
+ * or [ValueOrClosed] with close cause if channel was closed.
+ *
+ * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
+ * function is suspended, this function immediately resumes with [CancellationException].
+ *
+ * *Cancellation of suspended receive is atomic* -- when this function
+ * throws [CancellationException] it means that the element was not retrieved from this channel.
+ * As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
+ * continue to execute even after it was cancelled from the same thread in the case when this receive operation
+ * was already resumed and the continuation was posted for execution to the thread's queue.
+ *
+ * Note, that this function does not check for cancellation when it is not suspended.
+ * Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
+ *
+ * This function can be used in [select] invocation with [onReceiveOrClosed] clause.
+ * Use [poll] to try receiving from this channel without waiting.
+ *
+ * @suppress *This is an internal API, do not use*: Inline classes ABI is not stable yet and
+ * [KT-27524](https://youtrack.jetbrains.com/issue/KT-27524) needs to be fixed.
+ */
+ @InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
+ public suspend fun receiveOrClosed(): ValueOrClosed<E>
+
+ /**
+ * Clause for [select] expression of [receiveOrClosed] suspending function that selects with the [ValueOrClosed] with a value
+ * that is received from the channel or selects with [ValueOrClosed] with a close cause if the channel
+ * [isClosedForReceive].
+ *
+ * @suppress *This is an internal API, do not use*: Inline classes ABI is not stable yet and
+ * [KT-27524](https://youtrack.jetbrains.com/issue/KT-27524) needs to be fixed.
+ */
+ @InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
+ public val onReceiveOrClosed: SelectClause1<ValueOrClosed<E>>
+
+ /**
* Retrieves and removes the element from this channel, or returns `null` if this channel is empty
* or is [isClosedForReceive] without cause.
* It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
@@ -272,6 +322,107 @@
}
/**
+ * A discriminated union of [ReceiveChannel.receiveOrClosed] result,
+ * that encapsulates either successfully received element of type [T] from the channel or a close cause.
+ *
+ * :todo: Do not make it public before resolving todos in the code of this class.
+ *
+ * @suppress *This is an internal API, do not use*: Inline classes ABI is not stable yet and
+ * [KT-27524](https://youtrack.jetbrains.com/issue/KT-27524) needs to be fixed.
+ */
+@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
+@InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
+public inline class ValueOrClosed<out T>
+internal constructor(private val holder: Any?) {
+ /**
+ * Returns `true` if this instance represents received element.
+ * In this case [isClosed] returns `false`.
+ * todo: it is commented for now, because it is not used
+ */
+ //public val isValue: Boolean get() = holder !is Closed
+
+ /**
+ * Returns `true` if this instance represents close cause.
+ * In this case [isValue] returns `false`.
+ */
+ public val isClosed: Boolean get() = holder is Closed
+
+ /**
+ * Returns received value if this instance represents received value or throws [IllegalStateException] otherwise.
+ *
+ * :todo: Decide if it is needed how it shall be named with relation to [valueOrThrow]:
+ *
+ * So we have the following methods on ValueOrClosed: `value`, `valueOrNull`, `valueOrThrow`.
+ * On the other hand, the channel has the following receive variants:
+ * * `receive` which corresponds to `receiveOrClosed().valueOrThrow`... huh?
+ * * `receiveOrNull` which corresponds to `receiveOrClosed().valueOrNull`
+ * * `receiveOrClosed`
+ * For the sake of consider dropping this version of `value` and rename [valueOrThrow] to simply `value`.
+ */
+ @Suppress("UNCHECKED_CAST")
+ public val value: T
+ get() = if (holder is Closed) error(DEFAULT_CLOSE_MESSAGE) else holder as T
+
+ /**
+ * Returns received value if this element represents received value or `null` otherwise.
+ * :todo: Decide if it shall be made into extension that is available only for non-null T.
+ * Note: it might become inconsistent with kotlin.Result
+ */
+ @Suppress("UNCHECKED_CAST")
+ public val valueOrNull: T?
+ get() = if (holder is Closed) null else holder as T
+
+ /**
+ * :todo: Decide if it is needed how it shall be named with relation to [value].
+ * Note, that valueOrThrow rethrows the cause adding no meaningful information about the callsite,
+ * so if one is sure that ValueOrClosed is always value, this very property should be used.
+ * Otherwise, it could be very hard to locate the source of the exception.
+ * todo: it is commented for now, because it is not used
+ */
+ //@Suppress("UNCHECKED_CAST")
+ //public val valueOrThrow: T
+ // get() = if (holder is Closed) throw holder.exception else holder as T
+
+ /**
+ * Returns close cause of the channel if this instance represents close cause or throws
+ * [IllegalStateException] otherwise.
+ */
+ @Suppress("UNCHECKED_CAST")
+ public val closeCause: Throwable? get() =
+ if (holder is Closed) holder.cause else error("Channel was not closed")
+
+ /**
+ * @suppress
+ */
+ public override fun toString(): String =
+ when (holder) {
+ is Closed -> holder.toString()
+ else -> "Value($holder)"
+ }
+
+ internal class Closed(@JvmField val cause: Throwable?) {
+ // todo: it is commented for now, because it is not used
+ //val exception: Throwable get() = cause ?: ClosedReceiveChannelException(DEFAULT_CLOSE_MESSAGE)
+ override fun equals(other: Any?): Boolean = other is Closed && cause == other.cause
+ override fun hashCode(): Int = cause.hashCode()
+ override fun toString(): String = "Closed($cause)"
+ }
+
+ /**
+ * todo: consider making value/closed constructors public in the future.
+ */
+ internal companion object {
+ @Suppress("NOTHING_TO_INLINE")
+ internal inline fun <E> value(value: E): ValueOrClosed<E> =
+ ValueOrClosed(value)
+
+ @Suppress("NOTHING_TO_INLINE")
+ internal inline fun <E> closed(cause: Throwable?): ValueOrClosed<E> =
+ ValueOrClosed(Closed(cause))
+ }
+}
+
+/**
* Iterator for [ReceiveChannel]. Instances of this interface are *not thread-safe* and shall not be used
* from concurrent coroutines.
*/
diff --git a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
index 352c8c1..cd37bfb 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
@@ -8,6 +8,7 @@
package kotlinx.coroutines.channels
import kotlinx.coroutines.*
+import kotlinx.coroutines.selects.*
import kotlin.coroutines.*
import kotlin.jvm.*
@@ -34,6 +35,49 @@
}
/**
+ * Retrieves and removes the element from this channel suspending the caller while this channel [isEmpty]
+ * or returns `null` if the channel is [closed][Channel.isClosedForReceive].
+ *
+ * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
+ * function is suspended, this function immediately resumes with [CancellationException].
+ *
+ * *Cancellation of suspended receive is atomic* -- when this function
+ * throws [CancellationException] it means that the element was not retrieved from this channel.
+ * As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
+ * continue to execute even after it was cancelled from the same thread in the case when this receive operation
+ * was already resumed and the continuation was posted for execution to the thread's queue.
+ *
+ * Note, that this function does not check for cancellation when it is not suspended.
+ * Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
+ *
+ * This extension is defined only for channels on non-null types, so that generic functions defined using
+ * these extensions do not accidentally confuse `null` value and a normally closed channel, leading to hard
+ * to find bugs.
+ */
+@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
+@ExperimentalCoroutinesApi // since 1.3.0, tentatively stable in 1.4.0
+public suspend fun <E : Any> ReceiveChannel<E>.receiveOrNull(): E? {
+ @Suppress("DEPRECATION", "UNCHECKED_CAST")
+ return (this as ReceiveChannel<E?>).receiveOrNull()
+}
+
+/**
+ * Clause for [select] expression of [receiveOrNull] suspending function that selects with the element that
+ * is received from the channel or selects with `null` if the channel
+ * [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause. The [select] invocation fails with
+ * the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ *
+ * This extension is defined only for channels on non-null types, so that generic functions defined using
+ * these extensions do not accidentally confuse `null` value and a normally closed channel, leading to hard
+ * to find bugs.
+ **/
+@ExperimentalCoroutinesApi // since 1.3.0, tentatively stable in 1.4.0
+public fun <E : Any> ReceiveChannel<E>.onReceiveOrNull(): SelectClause1<E?> {
+ @Suppress("DEPRECATION", "UNCHECKED_CAST")
+ return (this as ReceiveChannel<E?>).onReceiveOrNull
+}
+
+/**
* Subscribes to this [BroadcastChannel] and performs the specified action for each received element.
*
* **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.**
diff --git a/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt
index 2d9858c..b242639 100644
--- a/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -142,11 +142,18 @@
private fun removeSubscriber(list: Array<Subscriber<E>>, subscriber: Subscriber<E>): Array<Subscriber<E>>? {
val n = list.size
val i = list.indexOf(subscriber)
- check(i >= 0)
+ assert { i >= 0 }
if (n == 1) return null
val update = arrayOfNulls<Subscriber<E>>(n - 1)
- arraycopy(list, 0, update, 0, i)
- arraycopy(list, i + 1, update, i, n - i - 1)
+ list.copyInto(
+ destination = update,
+ endIndex = i
+ )
+ list.copyInto(
+ destination = update,
+ destinationOffset = i,
+ startIndex = i + 1
+ )
return update as Array<Subscriber<E>>
}
diff --git a/kotlinx-coroutines-core/common/src/channels/Produce.kt b/kotlinx-coroutines-core/common/src/channels/Produce.kt
index 9e34773..a579d7a 100644
--- a/kotlinx-coroutines-core/common/src/channels/Produce.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Produce.kt
@@ -42,7 +42,7 @@
* ```
*/
@ExperimentalCoroutinesApi
-public suspend fun <T> ProducerScope<T>.awaitClose(block: () -> Unit = {}) {
+public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {
check(kotlin.coroutines.coroutineContext[Job] === this) { "awaitClose() can be invoke only from the producer context" }
try {
suspendCancellableCoroutine<Unit> { cont ->
diff --git a/kotlinx-coroutines-core/common/src/flow/Builders.kt b/kotlinx-coroutines-core/common/src/flow/Builders.kt
index f1a5c0b..41f13aa 100644
--- a/kotlinx-coroutines-core/common/src/flow/Builders.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Builders.kt
@@ -11,6 +11,7 @@
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.channels.Channel.Factory.BUFFERED
import kotlinx.coroutines.flow.internal.*
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
import kotlin.coroutines.*
import kotlin.jvm.*
@@ -44,25 +45,12 @@
* ```
* If you want to switch the context of execution of a flow, use the [flowOn] operator.
*/
-@ExperimentalCoroutinesApi
-public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> {
- return object : Flow<T> {
- override suspend fun collect(collector: FlowCollector<T>) {
- SafeCollector(collector, coroutineContext).block()
- }
- }
-}
+public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)
-/**
- * An analogue of the [flow] builder that does not check the context of execution of the resulting flow.
- * Used in our own operators where we trust the context of invocations.
- */
-@PublishedApi
-internal inline fun <T> unsafeFlow(@BuilderInference crossinline block: suspend FlowCollector<T>.() -> Unit): Flow<T> {
- return object : Flow<T> {
- override suspend fun collect(collector: FlowCollector<T>) {
- collector.block()
- }
+// Named anonymous object
+private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : Flow<T> {
+ override suspend fun collect(collector: FlowCollector<T>) {
+ SafeCollector(collector, coroutineContext).block()
}
}
@@ -70,7 +58,7 @@
* Creates a flow that produces a single value from the given functional type.
*/
@FlowPreview
-public fun <T> (() -> T).asFlow(): Flow<T> = unsafeFlow {
+public fun <T> (() -> T).asFlow(): Flow<T> = flow {
emit(invoke())
}
@@ -83,15 +71,14 @@
* ```
*/
@FlowPreview
-public fun <T> (suspend () -> T).asFlow(): Flow<T> = unsafeFlow {
+public fun <T> (suspend () -> T).asFlow(): Flow<T> = flow {
emit(invoke())
}
/**
* Creates a flow that produces values from the given iterable.
*/
-@ExperimentalCoroutinesApi
-public fun <T> Iterable<T>.asFlow(): Flow<T> = unsafeFlow {
+public fun <T> Iterable<T>.asFlow(): Flow<T> = flow {
forEach { value ->
emit(value)
}
@@ -100,8 +87,7 @@
/**
* Creates a flow that produces values from the given iterable.
*/
-@ExperimentalCoroutinesApi
-public fun <T> Iterator<T>.asFlow(): Flow<T> = unsafeFlow {
+public fun <T> Iterator<T>.asFlow(): Flow<T> = flow {
forEach { value ->
emit(value)
}
@@ -110,8 +96,7 @@
/**
* Creates a flow that produces values from the given sequence.
*/
-@ExperimentalCoroutinesApi
-public fun <T> Sequence<T>.asFlow(): Flow<T> = unsafeFlow {
+public fun <T> Sequence<T>.asFlow(): Flow<T> = flow {
forEach { value ->
emit(value)
}
@@ -120,8 +105,7 @@
/**
* Creates a flow that produces values from the given array of elements.
*/
-@ExperimentalCoroutinesApi
-public fun <T> flowOf(vararg elements: T): Flow<T> = unsafeFlow {
+public fun <T> flowOf(vararg elements: T): Flow<T> = flow {
for (element in elements) {
emit(element)
}
@@ -130,8 +114,7 @@
/**
* Creates flow that produces a given [value].
*/
-@ExperimentalCoroutinesApi
-public fun <T> flowOf(value: T): Flow<T> = unsafeFlow {
+public fun <T> flowOf(value: T): Flow<T> = flow {
/*
* Implementation note: this is just an "optimized" overload of flowOf(vararg)
* which significantly reduce the footprint of widespread single-value flows.
@@ -142,7 +125,6 @@
/**
* Returns an empty flow.
*/
-@ExperimentalCoroutinesApi
public fun <T> emptyFlow(): Flow<T> = EmptyFlow
private object EmptyFlow : Flow<Nothing> {
@@ -152,8 +134,7 @@
/**
* Creates a flow that produces values from the given array.
*/
-@ExperimentalCoroutinesApi
-public fun <T> Array<T>.asFlow(): Flow<T> = unsafeFlow {
+public fun <T> Array<T>.asFlow(): Flow<T> = flow {
forEach { value ->
emit(value)
}
@@ -162,8 +143,7 @@
/**
* Creates flow that produces values from the given array.
*/
-@ExperimentalCoroutinesApi
-public fun IntArray.asFlow(): Flow<Int> = unsafeFlow {
+public fun IntArray.asFlow(): Flow<Int> = flow {
forEach { value ->
emit(value)
}
@@ -172,8 +152,7 @@
/**
* Creates flow that produces values from the given array.
*/
-@ExperimentalCoroutinesApi
-public fun LongArray.asFlow(): Flow<Long> = unsafeFlow {
+public fun LongArray.asFlow(): Flow<Long> = flow {
forEach { value ->
emit(value)
}
@@ -182,8 +161,7 @@
/**
* Creates flow that produces values from the given range.
*/
-@ExperimentalCoroutinesApi
-public fun IntRange.asFlow(): Flow<Int> = unsafeFlow {
+public fun IntRange.asFlow(): Flow<Int> = flow {
forEach { value ->
emit(value)
}
@@ -192,7 +170,6 @@
/**
* Creates flow that produces values from the given range.
*/
-@ExperimentalCoroutinesApi
public fun LongRange.asFlow(): Flow<Long> = flow {
forEach { value ->
emit(value)
diff --git a/kotlinx-coroutines-core/common/src/flow/Channels.kt b/kotlinx-coroutines-core/common/src/flow/Channels.kt
index d0d2243..a554a4a 100644
--- a/kotlinx-coroutines-core/common/src/flow/Channels.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Channels.kt
@@ -7,14 +7,128 @@
package kotlinx.coroutines.flow
+import kotlinx.atomicfu.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
-import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
-import kotlinx.coroutines.channels.Channel.Factory.BUFFERED
-import kotlinx.coroutines.channels.Channel.Factory.OPTIONAL_CHANNEL
import kotlinx.coroutines.flow.internal.*
import kotlin.coroutines.*
import kotlin.jvm.*
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
+
+/**
+ * Emits all elements from the given [channel] to this flow collector and [cancels][cancel] (consumes)
+ * the channel afterwards. If you need to iterate over the channel without consuming it,
+ * a regular `for` loop should be used instead.
+ *
+ * This function provides a more efficient shorthand for `channel.consumeEach { value -> emit(value) }`.
+ * See [consumeEach][ReceiveChannel.consumeEach].
+ */
+@ExperimentalCoroutinesApi
+public suspend fun <T> FlowCollector<T>.emitAll(channel: ReceiveChannel<T>) {
+ // Manually inlined "consumeEach" implementation that does not use iterator but works via "receiveOrClosed".
+ // It has smaller and more efficient spilled state which also allows to implement a manual kludge to
+ // fix retention of the last emitted value.
+ // See https://youtrack.jetbrains.com/issue/KT-16222
+ // See https://github.com/Kotlin/kotlinx.coroutines/issues/1333
+ var cause: Throwable? = null
+ try {
+ while (true) {
+ // :KLUDGE: This "run" call is resolved to an extension function "run" and forces the size of
+ // spilled state to increase by an additional slot, so there are 4 object local variables spilled here
+ // which makes the size of spill state equal to the 4 slots that are spilled around subsequent "emit"
+ // call, ensuring that the previously emitted value is not retained in the state while receiving
+ // the next one.
+ // L$0 <- this
+ // L$1 <- channel
+ // L$2 <- cause
+ // L$3 <- this$run (actually equal to this)
+ val result = run { channel.receiveOrClosed() }
+ if (result.isClosed) {
+ result.closeCause?.let { throw it }
+ break // returns normally when result.closeCause == null
+ }
+ // result is spilled here to the coroutine state and retained after the call, even though
+ // it is not actually needed in the next loop iteration.
+ // L$0 <- this
+ // L$1 <- channel
+ // L$2 <- cause
+ // L$3 <- result
+ emit(result.value)
+ }
+ } catch (e: Throwable) {
+ cause = e
+ throw e
+ } finally {
+ channel.cancelConsumed(cause)
+ }
+}
+
+/**
+ * Represents the given receive channel as a hot flow and [consumes][ReceiveChannel.consume] the channel
+ * on the first collection from this flow. The resulting flow can be collected just once and throws
+ * [IllegalStateException] when trying to collect it more than once.
+ *
+ * ### Cancellation semantics
+ *
+ * 1) Flow consumer is cancelled when the original channel is cancelled.
+ * 2) Flow consumer completes normally when the original channel completes (~is closed) normally.
+ * 3) If the flow consumer fails with an exception, channel is cancelled.
+ *
+ * ### Operator fusion
+ *
+ * Adjacent applications of [flowOn], [buffer], [conflate], and [produceIn] to the result of `consumeAsFlow` are fused.
+ * In particular, [produceIn] returns the original channel (but throws [IllegalStateException] on repeated calls).
+ * Calls to [flowOn] have generally no effect, unless [buffer] is used to explicitly request buffering.
+ */
+@FlowPreview
+public fun <T> ReceiveChannel<T>.consumeAsFlow(): Flow<T> = ConsumeAsFlow(this)
+
+/**
+ * Represents an existing [channel] as [ChannelFlow] implementation.
+ * It fuses with subsequent [flowOn] operators, but for the most part ignores the specified context.
+ * However, additional [buffer] calls cause a separate buffering channel to be created and that is where
+ * the context might play a role, because it is used by the producing coroutine.
+ */
+private class ConsumeAsFlow<T>(
+ private val channel: ReceiveChannel<T>,
+ context: CoroutineContext = EmptyCoroutineContext,
+ capacity: Int = Channel.OPTIONAL_CHANNEL
+) : ChannelFlow<T>(context, capacity) {
+ private val consumed = atomic(false)
+
+ private fun markConsumed() =
+ check(!consumed.getAndSet(true)) { "ReceiveChannel.consumeAsFlow can be collected just once" }
+
+ override fun create(context: CoroutineContext, capacity: Int): ChannelFlow<T> =
+ ConsumeAsFlow(channel, context, capacity)
+
+ override suspend fun collectTo(scope: ProducerScope<T>) =
+ SendingCollector(scope).emitAll(channel) // use efficient channel receiving code from emitAll
+
+ override fun broadcastImpl(scope: CoroutineScope, start: CoroutineStart): BroadcastChannel<T> {
+ markConsumed() // fail fast on repeated attempt to collect it
+ return super.broadcastImpl(scope, start)
+ }
+
+ override fun produceImpl(scope: CoroutineScope): ReceiveChannel<T> {
+ markConsumed() // fail fast on repeated attempt to collect it
+ return if (capacity == Channel.OPTIONAL_CHANNEL) {
+ channel // direct
+ } else
+ super.produceImpl(scope) // extra buffering channel
+ }
+
+ override suspend fun collect(collector: FlowCollector<T>) {
+ if (capacity == Channel.OPTIONAL_CHANNEL) {
+ markConsumed()
+ collector.emitAll(channel) // direct
+ } else {
+ super.collect(collector) // extra buffering channel, produceImpl will mark it as consumed
+ }
+ }
+
+ override fun additionalToStringProps(): String = "channel=$channel, "
+}
/**
* Represents the given broadcast channel as a hot flow.
@@ -27,10 +141,7 @@
*/
@FlowPreview
public fun <T> BroadcastChannel<T>.asFlow(): Flow<T> = flow {
- val subscription = openSubscription()
- subscription.consumeEach { value ->
- emit(value)
- }
+ emitAll(openSubscription())
}
/**
diff --git a/kotlinx-coroutines-core/common/src/flow/Flow.kt b/kotlinx-coroutines-core/common/src/flow/Flow.kt
index b810305..bda326f 100644
--- a/kotlinx-coroutines-core/common/src/flow/Flow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Flow.kt
@@ -157,7 +157,6 @@
* Flow is [Reactive Streams](http://www.reactive-streams.org/) compliant, you can safely interop it with
* reactive streams using [Flow.asPublisher] and [Publisher.asFlow] from `kotlinx-coroutines-reactive` module.
*/
-@ExperimentalCoroutinesApi
public interface Flow<out T> {
/**
* Accepts the given [collector] and [emits][FlowCollector.emit] values into it.
diff --git a/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt b/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
index bb0d5b5..7254c6d 100644
--- a/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
+++ b/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
@@ -4,8 +4,6 @@
package kotlinx.coroutines.flow
-import kotlinx.coroutines.*
-
/**
* [FlowCollector] is used as an intermediate or a terminal collector of the flow and represents
* an entity that accepts values emitted by the [Flow].
@@ -13,7 +11,6 @@
* This interface should usually not be implemented directly, but rather used as a receiver in a [flow] builder when implementing a custom operator.
* Implementations of this interface are not thread-safe.
*/
-@ExperimentalCoroutinesApi
public interface FlowCollector<in T> {
/**
diff --git a/kotlinx-coroutines-core/common/src/flow/Migration.kt b/kotlinx-coroutines-core/common/src/flow/Migration.kt
index bc45b01..b7e91f5 100644
--- a/kotlinx-coroutines-core/common/src/flow/Migration.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Migration.kt
@@ -1,15 +1,26 @@
/*
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
@file:Suppress("unused", "DeprecatedCallableAddReplaceWith", "UNUSED_PARAMETER")
+
package kotlinx.coroutines.flow
import kotlin.coroutines.*
+import kotlin.jvm.*
/**
+ * **GENERAL NOTE**
+ *
* These deprecations are added to improve user experience when they will start to
* search for their favourite operators and/or patterns that are missing or renamed in Flow.
+ * Deprecated functions also are moved here when they renamed. The difference is that they have
+ * a body with their implementation while pure stubs have [noImpl].
*/
+private fun noImpl(): Nothing =
+ throw UnsupportedOperationException("Not implemented, should not be called")
/**
* `observeOn` has no direct match in [Flow] API because all terminal flow operators are suspending and
@@ -33,7 +44,7 @@
* @suppress
*/
@Deprecated(message = "Collect flow in the desired context instead", level = DeprecationLevel.ERROR)
-public fun <T> Flow<T>.observeOn(context: CoroutineContext): Flow<T> = error("Should not be called")
+public fun <T> Flow<T>.observeOn(context: CoroutineContext): Flow<T> = noImpl()
/**
* `publishOn` has no direct match in [Flow] API because all terminal flow operators are suspending and
@@ -57,7 +68,7 @@
* @suppress
*/
@Deprecated(message = "Collect flow in the desired context instead", level = DeprecationLevel.ERROR)
-public fun <T> Flow<T>.publishOn(context: CoroutineContext): Flow<T> = error("Should not be called")
+public fun <T> Flow<T>.publishOn(context: CoroutineContext): Flow<T> = noImpl()
/**
* `subscribeOn` has no direct match in [Flow] API because [Flow] preserves its context and does not leak it.
@@ -86,36 +97,54 @@
* @suppress
*/
@Deprecated(message = "Use flowOn instead", level = DeprecationLevel.ERROR)
-public fun <T> Flow<T>.subscribeOn(context: CoroutineContext): Flow<T> = error("Should not be called")
+public fun <T> Flow<T>.subscribeOn(context: CoroutineContext): Flow<T> = noImpl()
-/** @suppress **/
+/**
+ * Use [BroadcastChannel][kotlinx.coroutines.channels.BroadcastChannel].asFlow().
+ * @suppress
+ */
@Deprecated(message = "Use BroadcastChannel.asFlow()", level = DeprecationLevel.ERROR)
-public fun BehaviourSubject(): Any = error("Should not be called")
+public fun BehaviourSubject(): Any = noImpl()
-/** @suppress **/
+/**
+ * `ReplaySubject` is not supported. The closest analogue is buffered [BroadcastChannel][kotlinx.coroutines.channels.BroadcastChannel].
+ * @suppress
+ */
@Deprecated(
message = "ReplaySubject is not supported. The closest analogue is buffered broadcast channel",
level = DeprecationLevel.ERROR)
-public fun ReplaySubject(): Any = error("Should not be called")
+public fun ReplaySubject(): Any = noImpl()
-/** @suppress **/
+/**
+ * `PublishSubject` is not supported.
+ * @suppress
+ */
@Deprecated(message = "PublishSubject is not supported", level = DeprecationLevel.ERROR)
-public fun PublishSubject(): Any = error("Should not be called")
+public fun PublishSubject(): Any = noImpl()
-/** @suppress **/
+/**
+ * Flow analogue of `onErrorXxx` is [catch].
+ * Use `catch { emitAll(fallback) }`.
+ * @suppress
+ */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'onErrorXxx' is 'catch'. Use 'catch { emitAll(fallback) }'",
replaceWith = ReplaceWith("catch { emitAll(fallback) }")
)
-public fun <T> Flow<T>.onErrorResume(fallback: Flow<T>): Flow<T> = error("Should not be called")
+public fun <T> Flow<T>.onErrorResume(fallback: Flow<T>): Flow<T> = noImpl()
+/**
+ * Flow analogue of `onErrorXxx` is [catch].
+ * Use `catch { emitAll(fallback) }`.
+ * @suppress
+ */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'onErrorXxx' is 'catch'. Use 'catch { emitAll(fallback) }'",
replaceWith = ReplaceWith("catch { emitAll(fallback) }")
)
-public fun <T> Flow<T>.onErrorResumeNext(fallback: Flow<T>): Flow<T> = error("Should not be called")
+public fun <T> Flow<T>.onErrorResumeNext(fallback: Flow<T>): Flow<T> = noImpl()
/**
* Self-explanatory, the reason of deprecation is "context preservation" property (you can read more in [Flow] documentation)
@@ -123,7 +152,7 @@
**/
@Suppress("UNUSED_PARAMETER", "UNUSED", "DeprecatedCallableAddReplaceWith")
@Deprecated(message = "withContext in flow body is deprecated, use flowOn instead", level = DeprecationLevel.ERROR)
-public fun <T, R> FlowCollector<T>.withContext(context: CoroutineContext, block: suspend () -> R): Unit = error("Should not be called")
+public fun <T, R> FlowCollector<T>.withContext(context: CoroutineContext, block: suspend () -> R): Unit = noImpl()
/**
* `subscribe` is Rx-specific API that has no direct match in flows.
@@ -153,19 +182,25 @@
message = "Use launchIn with onEach, onCompletion and catch operators instead",
level = DeprecationLevel.ERROR
)
-public fun <T> Flow<T>.subscribe(): Unit = error("Should not be called")
+public fun <T> Flow<T>.subscribe(): Unit = noImpl()
-/** @suppress **/
+/**
+ * Use [launchIn] with [onEach], [onCompletion] and [catch] operators instead.
+ * @suppress
+ */
@Deprecated(
message = "Use launchIn with onEach, onCompletion and catch operators instead",
level = DeprecationLevel.ERROR
-)public fun <T> Flow<T>.subscribe(onEach: suspend (T) -> Unit): Unit = error("Should not be called")
+)public fun <T> Flow<T>.subscribe(onEach: suspend (T) -> Unit): Unit = noImpl()
-/** @suppress **/
+/**
+ * Use [launchIn] with [onEach], [onCompletion] and [catch] operators instead.
+ * @suppress
+ */
@Deprecated(
message = "Use launchIn with onEach, onCompletion and catch operators instead",
level = DeprecationLevel.ERROR
-)public fun <T> Flow<T>.subscribe(onEach: suspend (T) -> Unit, onError: suspend (Throwable) -> Unit): Unit = error("Should not be called")
+)public fun <T> Flow<T>.subscribe(onEach: suspend (T) -> Unit, onError: suspend (Throwable) -> Unit): Unit = noImpl()
/**
* Note that this replacement is sequential (`concat`) by default.
@@ -177,15 +212,18 @@
message = "Flow analogue is named flatMapConcat",
replaceWith = ReplaceWith("flatMapConcat(mapper)")
)
-public fun <T, R> Flow<T>.flatMap(mapper: suspend (T) -> Flow<R>): Flow<R> = error("Should not be called")
+public fun <T, R> Flow<T>.flatMap(mapper: suspend (T) -> Flow<R>): Flow<R> = noImpl()
-/** @suppress **/
+/**
+ * Flow analogue of `concatMap` is [flatMapConcat].
+ * @suppress
+ */
@Deprecated(
level = DeprecationLevel.ERROR,
- message = "Flow analogue is named flatMapConcat",
+ message = "Flow analogue of 'concatMap' is 'flatMapConcat'",
replaceWith = ReplaceWith("flatMapConcat(mapper)")
)
-public fun <T, R> Flow<T>.concatMap(mapper: (T) -> Flow<R>): Flow<R> = error("Should not be called")
+public fun <T, R> Flow<T>.concatMap(mapper: (T) -> Flow<R>): Flow<R> = noImpl()
/**
* Note that this replacement is sequential (`concat`) by default.
@@ -197,15 +235,18 @@
message = "Flow analogue of 'merge' is 'flattenConcat'",
replaceWith = ReplaceWith("flattenConcat()")
)
-public fun <T> Flow<Flow<T>>.merge(): Flow<T> = error("Should not be called")
+public fun <T> Flow<Flow<T>>.merge(): Flow<T> = noImpl()
-/** @suppress **/
+/**
+ * Flow analogue of `flatten` is [flattenConcat].
+ * @suppress
+ */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'flatten' is 'flattenConcat'",
replaceWith = ReplaceWith("flattenConcat()")
)
-public fun <T> Flow<Flow<T>>.flatten(): Flow<T> = error("Should not be called")
+public fun <T> Flow<Flow<T>>.flatten(): Flow<T> = noImpl()
/**
* Kotlin has a built-in generic mechanism for making chained calls.
@@ -218,7 +259,6 @@
* ```
* myFlow.let(MyFlowExtensions.ignoreErrors()).collect { ... }
* ```
- *
* @suppress
*/
@Deprecated(
@@ -226,9 +266,10 @@
message = "Flow analogue of 'compose' is 'let'",
replaceWith = ReplaceWith("let(transformer)")
)
-public fun <T, R> Flow<T>.compose(transformer: Flow<T>.() -> Flow<R>): Flow<R> = error("Should not be called")
+public fun <T, R> Flow<T>.compose(transformer: Flow<T>.() -> Flow<R>): Flow<R> = noImpl()
/**
+ * Flow analogue of `skip` is [drop].
* @suppress
*/
@Deprecated(
@@ -236,7 +277,7 @@
message = "Flow analogue of 'skip' is 'drop'",
replaceWith = ReplaceWith("drop(count)")
)
-public fun <T> Flow<T>.skip(count: Int): Flow<T> = error("Should not be called")
+public fun <T> Flow<T>.skip(count: Int): Flow<T> = noImpl()
/**
* Flow extension to iterate over elements is [collect].
@@ -251,23 +292,38 @@
message = "Flow analogue of 'forEach' is 'collect'",
replaceWith = ReplaceWith("collect(block)")
)
-public fun <T> Flow<T>.forEach(action: suspend (value: T) -> Unit): Unit = error("Should not be called")
+public fun <T> Flow<T>.forEach(action: suspend (value: T) -> Unit): Unit = noImpl()
+/**
+ * Flow has less verbose [scan] shortcut.
+ * @suppress
+ */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow has less verbose 'scan' shortcut",
replaceWith = ReplaceWith("scan(initial, operation)")
)
-public fun <T, R> Flow<T>.scanFold(initial: R, @BuilderInference operation: suspend (accumulator: R, value: T) -> R): Flow<R> = error("Should not be called")
+public fun <T, R> Flow<T>.scanFold(initial: R, @BuilderInference operation: suspend (accumulator: R, value: T) -> R): Flow<R> =
+ noImpl()
+/**
+ * Flow analogue of `onErrorXxx` is [catch].
+ * Use `catch { emit(fallback) }`.
+ * @suppress
+ */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'onErrorXxx' is 'catch'. Use 'catch { emit(fallback) }'",
replaceWith = ReplaceWith("catch { emit(fallback) }")
)
// Note: this version without predicate gives better "replaceWith" action
-public fun <T> Flow<T>.onErrorReturn(fallback: T): Flow<T> = error("Should not be called")
+public fun <T> Flow<T>.onErrorReturn(fallback: T): Flow<T> = noImpl()
+/**
+ * Flow analogue of `onErrorXxx` is [catch].
+ * Use `catch { e -> if (predicate(e)) emit(fallback) else throw e }`.
+ * @suppress
+ */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'onErrorXxx' is 'catch'. Use 'catch { e -> if (predicate(e)) emit(fallback) else throw e }'",
@@ -279,3 +335,52 @@
if (!predicate(e)) throw e
emit(fallback)
}
+
+/**
+ * Flow analogue of `startWith` is [onStart].
+ * Use `onStart { emit(value) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'startWith' is 'onStart'. Use 'onStart { emit(value) }'",
+ replaceWith = ReplaceWith("onStart { emit(value) }")
+)
+public fun <T> Flow<T>.startWith(value: T): Flow<T> = noImpl()
+
+/**
+ * Flow analogue of `startWith` is [onStart].
+ * Use `onStart { emitAll(other) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'startWith' is 'onStart'. Use 'onStart { emitAll(other) }'",
+ replaceWith = ReplaceWith("onStart { emitAll(other) }")
+)
+public fun <T> Flow<T>.startWith(other: Flow<T>): Flow<T> = noImpl()
+
+/**
+ * Flow analogue of `concatWith` is [onCompletion].
+ * Use `onCompletion { emit(value) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'concatWith' is 'onCompletion'. Use 'onCompletion { emit(value) }'",
+ replaceWith = ReplaceWith("onCompletion { emit(value) }")
+)
+public fun <T> Flow<T>.concatWith(value: T): Flow<T> = noImpl()
+
+/**
+ * Flow analogue of `concatWith` is [onCompletion].
+ * Use `onCompletion { emitAll(other) }`.
+ * @suppress
+ */
+@Deprecated(
+ level = DeprecationLevel.ERROR,
+ message = "Flow analogue of 'concatWith' is 'onCompletion'. Use 'onCompletion { emitAll(other) }'",
+ replaceWith = ReplaceWith("onCompletion { emitAkk(other) }")
+)
+public fun <T> Flow<T>.concatWith(other: Flow<T>): Flow<T> = noImpl()
+
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt b/kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt
index e676869..99a3bdc 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/ChannelFlow.kt
@@ -42,8 +42,8 @@
capacity == Channel.CONFLATED -> Channel.CONFLATED
else -> {
// sanity checks
- check(this.capacity >= 0) { "Unexpected capacity ${this.capacity}" }
- check(capacity >= 0) { "Unexpected capacity $capacity" }
+ assert { this.capacity >= 0 }
+ assert { capacity >= 0 }
// combine capacities clamping to UNLIMITED on overflow
val sum = this.capacity + capacity
if (sum >= 0) sum else Channel.UNLIMITED // unlimited on int overflow
@@ -64,7 +64,7 @@
private val produceCapacity: Int
get() = if (capacity == Channel.OPTIONAL_CHANNEL) Channel.BUFFERED else capacity
- fun broadcastImpl(scope: CoroutineScope, start: CoroutineStart): BroadcastChannel<T> =
+ open fun broadcastImpl(scope: CoroutineScope, start: CoroutineStart): BroadcastChannel<T> =
scope.broadcast(context, produceCapacity, start, block = collectToFun)
open fun produceImpl(scope: CoroutineScope): ReceiveChannel<T> =
@@ -72,8 +72,7 @@
override suspend fun collect(collector: FlowCollector<T>) =
coroutineScope {
- val channel = produceImpl(this)
- channel.consumeEach { collector.emit(it) }
+ collector.emitAll(produceImpl(this))
}
// debug toString
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt b/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
index 258869f..f0b5b39 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
@@ -11,7 +11,7 @@
import kotlinx.coroutines.intrinsics.*
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
-import kotlinx.coroutines.flow.unsafeFlow as flow
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
/**
* Creates a [CoroutineScope] and calls the specified suspend block with this scope.
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt b/kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt
index 6c675b3..c3a85a3 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/FlowExceptions.common.kt
@@ -16,3 +16,12 @@
* Exception used to cancel child of [scopedFlow] without cancelling the whole scope.
*/
internal expect class ChildCancelledException() : CancellationException
+
+@Suppress("NOTHING_TO_INLINE")
+@PublishedApi
+internal inline fun checkIndexOverflow(index: Int): Int {
+ if (index < 0) {
+ throw ArithmeticException("Index overflow has happened")
+ }
+ return index
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt b/kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt
index 297d4d1..f83f313 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/NopCollector.kt
@@ -4,8 +4,6 @@
package kotlinx.coroutines.flow.internal
-import kotlinx.coroutines.flow.*
-
internal object NopCollector : ConcurrentFlowCollector<Any?> {
override suspend fun emit(value: Any?) {
// does nothing
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.kt b/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.kt
index 5604dc5..09a6378 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.kt
@@ -9,7 +9,6 @@
import kotlinx.coroutines.internal.*
import kotlin.coroutines.*
-@PublishedApi
internal class SafeCollector<T>(
private val collector: FlowCollector<T>,
private val collectContext: CoroutineContext
@@ -99,3 +98,16 @@
return parent.transitiveCoroutineParent(collectJob)
}
}
+
+/**
+ * An analogue of the [flow] builder that does not check the context of execution of the resulting flow.
+ * Used in our own operators where we trust the context of invocations.
+ */
+@PublishedApi
+internal inline fun <T> unsafeFlow(@BuilderInference crossinline block: suspend FlowCollector<T>.() -> Unit): Flow<T> {
+ return object : Flow<T> {
+ override suspend fun collect(collector: FlowCollector<T>) {
+ collector.block()
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Context.kt b/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
index c9aa555..8f3325c 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
@@ -265,4 +265,3 @@
"Flow context cannot contain job in it. Had $context"
}
}
-
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt b/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
index 2f01061..8d74be5 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
@@ -12,7 +12,7 @@
import kotlinx.coroutines.flow.internal.*
import kotlinx.coroutines.selects.*
import kotlin.jvm.*
-import kotlinx.coroutines.flow.unsafeFlow as flow
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
/**
* Delays the emission of values from this flow for the given [timeMillis].
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt b/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt
index 45e971e..89491f4 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt
@@ -10,7 +10,7 @@
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.internal.*
import kotlin.jvm.*
-import kotlinx.coroutines.flow.unsafeFlow as flow
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
/**
* Returns flow where all subsequent repetitions of the same value are filtered out.
@@ -19,16 +19,36 @@
public fun <T> Flow<T>.distinctUntilChanged(): Flow<T> = distinctUntilChangedBy { it }
/**
+ * Returns flow where all subsequent repetitions of the same value are filtered out, when compared
+ * with each other via the provided [areEquivalent] function.
+ */
+@FlowPreview
+public fun <T> Flow<T>.distinctUntilChanged(areEquivalent: (old: T, new: T) -> Boolean): Flow<T> =
+ distinctUntilChangedBy(keySelector = { it }, areEquivalent = areEquivalent)
+
+/**
* Returns flow where all subsequent repetitions of the same key are filtered out, where
* key is extracted with [keySelector] function.
*/
@FlowPreview
public fun <T, K> Flow<T>.distinctUntilChangedBy(keySelector: (T) -> K): Flow<T> =
+ distinctUntilChangedBy(keySelector = keySelector, areEquivalent = { old, new -> old == new })
+
+/**
+ * Returns flow where all subsequent repetitions of the same key are filtered out, where
+ * keys are extracted with [keySelector] function and compared with each other via the
+ * provided [areEquivalent] function.
+ */
+private inline fun <T, K> Flow<T>.distinctUntilChangedBy(
+ crossinline keySelector: (T) -> K,
+ crossinline areEquivalent: (old: K, new: K) -> Boolean
+): Flow<T> =
flow {
var previousKey: Any? = NULL
collect { value ->
val key = keySelector(value)
- if (previousKey != key) {
+ @Suppress("UNCHECKED_CAST")
+ if (previousKey === NULL || !areEquivalent(previousKey as K, key)) {
previousKey = key
emit(value)
}
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt b/kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt
new file mode 100644
index 0000000..f3a1126
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+@file:Suppress("UNCHECKED_CAST")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.internal.*
+import kotlin.coroutines.*
+import kotlin.jvm.*
+
+// ------------------ WARNING ------------------
+// These emitting operators must use safe flow builder, because their allow
+// user code to directly emit to the underlying FlowCollector.
+
+/**
+ * Applies [transform] function to each value of the given flow.
+ *
+ * The receiver of the [transform] is [FlowCollector] and thus `transform` is a
+ * generic function that may transform emitted element, skip it or emit it multiple times.
+ *
+ * This operator can be used as a building block for other operators, for example:
+ *
+ * ```
+ * fun Flow<Int>.skipOddAndDuplicateEven(): Flow<Int> = transform { value ->
+ * if (value % 2 == 0) { // Emit only even values, but twice
+ * emit(value)
+ * emit(value)
+ * } // Do nothing if odd
+ * }
+ * ```
+ */
+@ExperimentalCoroutinesApi
+public inline fun <T, R> Flow<T>.transform(
+ @BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
+): Flow<R> = flow { // Note: safe flow is used here, because collector is exposed to transform on each operation
+ collect { value ->
+ // kludge, without it Unit will be returned and TCE won't kick in, KT-28938
+ return@collect transform(value)
+ }
+}
+
+// For internal operator implementation
+@PublishedApi
+internal inline fun <T, R> Flow<T>.unsafeTransform(
+ @BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
+): Flow<R> = unsafeFlow { // Note: unsafe flow is used here, because unsafeTransform is only for internal use
+ collect { value ->
+ // kludge, without it Unit will be returned and TCE won't kick in, KT-28938
+ return@collect transform(value)
+ }
+}
+
+/**
+ * Invokes the given [action] when the this flow starts to be collected.
+ *
+ * The receiver of the [action] is [FlowCollector] and thus `onStart` can emit additional elements.
+ * For example:
+ *
+ * ```
+ * flowOf("a", "b", "c")
+ * .onStart { emit("Begin") }
+ * .collect { println(it) } // prints Begin, a, b, c
+ * ```
+ */
+@ExperimentalCoroutinesApi // tentatively stable in 1.3.0
+public fun <T> Flow<T>.onStart(
+ action: suspend FlowCollector<T>.() -> Unit
+): Flow<T> = unsafeFlow { // Note: unsafe flow is used here, but safe collector is used to invoke start action
+ SafeCollector<T>(this, coroutineContext).action()
+ collect(this) // directly delegate
+}
+
+/**
+ * Invokes the given [action] when the given flow is completed or cancelled, using
+ * the exception from the upstream (if any) as cause parameter of [action].
+ *
+ * Conceptually, `onCompletion` is similar to wrapping the flow collection into a `finally` block,
+ * for example the following imperative snippet:
+ *
+ * ```
+ * try {
+ * myFlow.collect { value ->
+ * println(value)
+ * }
+ * } finally {
+ * println("Done")
+ * }
+ * ```
+ *
+ * can be replaced with a declarative one using `onCompletion`:
+ *
+ * ```
+ * myFlow
+ * .onEach { println(it) }
+ * .onCompletion { println("Done") }
+ * .collect()
+ * ```
+ *
+ * This operator is *transparent* to exceptions that occur in downstream flow
+ * and does not observe exceptions that are thrown to cancel the flow,
+ * while any exception from the [action] will be thrown downstream.
+ * This behaviour can be demonstrated by the following example:
+ *
+ * ```
+ * flow { emitData() }
+ * .map { computeOne(it) }
+ * .onCompletion { println(it) } // Can print exceptions from emitData and computeOne
+ * .map { computeTwo(it) }
+ * .onCompletion { println(it) } // Can print exceptions from emitData, computeOne, onCompletion and computeTwo
+ * .collect()
+ * ```
+ *
+ * The receiver of the [action] is [FlowCollector] and this operator can be used to emit additional
+ * elements at the end of the collection. For example:
+ *
+ * ```
+ * flowOf("a", "b", "c")
+ * .onCompletion { emit("Done") }
+ * .collect { println(it) } // prints a, b, c, Done
+ * ```
+ */
+@ExperimentalCoroutinesApi // tentatively stable in 1.3.0
+public fun <T> Flow<T>.onCompletion(
+ action: suspend FlowCollector<T>.(cause: Throwable?) -> Unit
+): Flow<T> = unsafeFlow { // Note: unsafe flow is used here, but safe collector is used to invoke completion action
+ var exception: Throwable? = null
+ try {
+ exception = catchImpl(this)
+ } finally {
+ // Separate method because of KT-32220
+ SafeCollector<T>(this, coroutineContext).invokeSafely(action, exception)
+ exception?.let { throw it }
+ }
+}
+
+// It was only released in 1.3.0-M2, remove in 1.4.0
+/** @suppress */
+@Deprecated(level = DeprecationLevel.HIDDEN, message = "binary compatibility with a version w/o FlowCollector receiver")
+public fun <T> Flow<T>.onCompletion(action: suspend (cause: Throwable?) -> Unit) =
+ onCompletion { action(it) }
+
+private suspend fun <T> FlowCollector<T>.invokeSafely(
+ action: suspend FlowCollector<T>.(cause: Throwable?) -> Unit,
+ cause: Throwable?
+) {
+ try {
+ action(cause)
+ } catch (e: Throwable) {
+ if (cause !== null) e.addSuppressedThrowable(cause)
+ throw e
+ }
+}
+
+
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt b/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
index c744842..9b7a91f 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
@@ -11,7 +11,7 @@
import kotlinx.coroutines.internal.*
import kotlin.coroutines.*
import kotlin.jvm.*
-import kotlinx.coroutines.flow.unsafeFlow as flow
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
/**
* Catches exceptions in the flow completion and calls a specified [action] with
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt b/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
index f633612..7f638f9 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
@@ -2,7 +2,6 @@
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-
@file:JvmMultifileClass
@file:JvmName("FlowKt")
@@ -11,7 +10,7 @@
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.internal.*
import kotlin.jvm.*
-import kotlinx.coroutines.flow.unsafeFlow as flow
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
/**
* Returns a flow that ignores first [count] elements.
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt b/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
index 3ed2c0b..e593d03 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
@@ -16,7 +16,7 @@
import kotlinx.coroutines.sync.*
import kotlin.coroutines.*
import kotlin.jvm.*
-import kotlinx.coroutines.flow.unsafeFlow as flow
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
/**
* Name of the property that defines the value of [DEFAULT_CONCURRENCY].
@@ -125,7 +125,7 @@
* ```
* produces `aa bb b_last`
*/
-@ExperimentalCoroutinesApi
+@FlowPreview
public fun <T, R> Flow<T>.switchMap(transform: suspend (value: T) -> Flow<R>): Flow<R> = scopedFlow { downstream ->
var previousFlow: Job? = null
collect { value ->
@@ -167,7 +167,7 @@
// Fast path in ChannelFlowOperator calls this function (channel was not created yet)
override suspend fun flowCollect(collector: FlowCollector<T>) {
// this function should not have been invoked when channel was explicitly requested
- check(capacity == OPTIONAL_CHANNEL)
+ assert { capacity == OPTIONAL_CHANNEL }
flowScope {
mergeImpl(this, collector.asConcurrentFlowCollector())
}
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
index f6b32b3..37ba0d3 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
@@ -10,73 +10,40 @@
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.internal.*
-import kotlin.coroutines.*
import kotlin.jvm.*
-import kotlinx.coroutines.flow.unsafeFlow as flow
-
-/**
- * Applies [transform] function to each value of the given flow.
- * [transform] is a generic function that may transform emitted element, skip it or emit it multiple times.
- *
- * This operator is useless by itself, but can be used as a building block of user-specific operators:
- * ```
- * fun Flow<Int>.skipOddAndDuplicateEven(): Flow<Int> = transform { value ->
- * if (value % 2 == 0) { // Emit only even values, but twice
- * emit(value)
- * emit(value)
- * } // Do nothing if odd
- * }
- * ```
- */
-@ExperimentalCoroutinesApi
-public inline fun <T, R> Flow<T>.transform(@BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit): Flow<R> {
- return flow {
- collect { value ->
- // kludge, without it Unit will be returned and TCE won't kick in, KT-28938
- return@collect transform(value)
- }
- }
-}
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
+import kotlinx.coroutines.flow.unsafeTransform as transform
/**
* Returns a flow containing only values of the original flow that matches the given [predicate].
*/
-@ExperimentalCoroutinesApi
-public inline fun <T> Flow<T>.filter(crossinline predicate: suspend (T) -> Boolean): Flow<T> = flow {
- collect { value ->
- if (predicate(value)) return@collect emit(value)
- }
+public inline fun <T> Flow<T>.filter(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
+ if (predicate(value)) return@transform emit(value)
}
/**
* Returns a flow containing only values of the original flow that do not match the given [predicate].
*/
-@ExperimentalCoroutinesApi
-public inline fun <T> Flow<T>.filterNot(crossinline predicate: suspend (T) -> Boolean): Flow<T> = flow {
- collect { value ->
- if (!predicate(value)) return@collect emit(value)
- }
+public inline fun <T> Flow<T>.filterNot(crossinline predicate: suspend (T) -> Boolean): Flow<T> = transform { value ->
+ if (!predicate(value)) return@transform emit(value)
}
/**
* Returns a flow containing only values that are instances of specified type [R].
*/
-@ExperimentalCoroutinesApi
@Suppress("UNCHECKED_CAST")
public inline fun <reified R> Flow<*>.filterIsInstance(): Flow<R> = filter { it is R } as Flow<R>
/**
* Returns a flow containing only values of the original flow that are not null.
*/
-@ExperimentalCoroutinesApi
-public fun <T: Any> Flow<T?>.filterNotNull(): Flow<T> = flow<T> {
- collect { value -> if (value != null) return@collect emit(value) }
+public fun <T: Any> Flow<T?>.filterNotNull(): Flow<T> = transform<T?, T> { value ->
+ if (value != null) return@transform emit(value)
}
/**
* Returns a flow containing the results of applying the given [transform] function to each value of the original flow.
*/
-@ExperimentalCoroutinesApi
public inline fun <T, R> Flow<T>.map(crossinline transform: suspend (value: T) -> R): Flow<R> = transform { value ->
return@transform emit(transform(value))
}
@@ -84,79 +51,28 @@
/**
* Returns a flow that contains only non-null results of applying the given [transform] function to each value of the original flow.
*/
-@ExperimentalCoroutinesApi
public inline fun <T, R: Any> Flow<T>.mapNotNull(crossinline transform: suspend (value: T) -> R?): Flow<R> = transform { value ->
val transformed = transform(value) ?: return@transform
return@transform emit(transformed)
}
/**
- * Returns a flow which performs the given [action] on each value of the original flow.
+ * Returns a flow that wraps each element into [IndexedValue], containing value and its index (starting from zero).
*/
@ExperimentalCoroutinesApi
-public fun <T> Flow<T>.onEach(action: suspend (T) -> Unit): Flow<T> = flow {
+public fun <T> Flow<T>.withIndex(): Flow<IndexedValue<T>> = flow {
+ var index = 0
collect { value ->
- action(value)
- emit(value)
+ emit(IndexedValue(checkIndexOverflow(index++), value))
}
}
/**
- * Invokes the given [action] when the given flow is completed or cancelled, using
- * the exception from the upstream (if any) as cause parameter of [action].
- *
- * Conceptually, [onCompletion] is similar to wrapping the flow collection into a `finally` block,
- * for example the following imperative snippet:
- * ```
- * try {
- * myFlow.collect { value ->
- * println(value)
- * }
- * } finally {
- * println("Done")
- * }
- * ```
- *
- * can be replaced with a declarative one using [onCompletion]:
- * ```
- * myFlow
- * .onEach { println(it) }
- * .onCompletion { println("Done") }
- * .collect()
- * ```
- *
- * This operator is *transparent* to exceptions that occur in downstream flow
- * and does not observe exceptions that are thrown to cancel the flow,
- * while any exception from the [action] will be thrown downstream.
- * This behaviour can be demonstrated by the following example:
- * ```
- * flow { emitData() }
- * .map { computeOne(it) }
- * .onCompletion { println(it) } // Can print exceptions from emitData and computeOne
- * .map { computeTwo(it) }
- * .onCompletion { println(it) } // Can print exceptions from emitData, computeOne, onCompletion and computeTwo
- * .collect()
- * ```
+ * Returns a flow which performs the given [action] on each value of the original flow.
*/
-@ExperimentalCoroutinesApi // tentatively stable in 1.3.0
-public fun <T> Flow<T>.onCompletion(action: suspend (cause: Throwable?) -> Unit): Flow<T> = flow {
- var exception: Throwable? = null
- try {
- exception = catchImpl(this)
- } finally {
- // Separate method because of KT-32220
- invokeSafely(action, exception)
- exception?.let { throw it }
- }
-}
-
-private suspend fun invokeSafely(action: suspend (cause: Throwable?) -> Unit, cause: Throwable?) {
- try {
- action(cause)
- } catch (e: Throwable) {
- if (cause !== null) e.addSuppressedThrowable(cause)
- throw e
- }
+public fun <T> Flow<T>.onEach(action: suspend (T) -> Unit): Flow<T> = transform { value ->
+ action(value)
+ return@transform emit(value)
}
/**
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Zip.kt b/kotlinx-coroutines-core/common/src/flow/operators/Zip.kt
index e9d99d3..72822bb 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Zip.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Zip.kt
@@ -13,7 +13,7 @@
import kotlinx.coroutines.flow.internal.*
import kotlinx.coroutines.selects.*
import kotlin.jvm.*
-import kotlinx.coroutines.flow.unsafeFlow as flow
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
/**
* Returns a [Flow] whose values are generated with [transform] function by combining
@@ -28,7 +28,7 @@
* }
* ```
*/
-@ExperimentalCoroutinesApi
+@FlowPreview
public fun <T1, T2, R> Flow<T1>.combineLatest(other: Flow<T2>, transform: suspend (T1, T2) -> R): Flow<R> = flow {
coroutineScope {
val firstChannel = asFairChannel(this@combineLatest)
@@ -80,7 +80,7 @@
* Returns a [Flow] whose values are generated with [transform] function by combining
* the most recently emitted values by each flow.
*/
-@ExperimentalCoroutinesApi
+@FlowPreview
public inline fun <T1, T2, T3, R> Flow<T1>.combineLatest(
other: Flow<T2>,
other2: Flow<T3>,
@@ -97,7 +97,7 @@
* Returns a [Flow] whose values are generated with [transform] function by combining
* the most recently emitted values by each flow.
*/
-@ExperimentalCoroutinesApi
+@FlowPreview
public inline fun <T1, T2, T3, T4, R> Flow<T1>.combineLatest(
other: Flow<T2>,
other2: Flow<T3>,
@@ -116,7 +116,7 @@
* Returns a [Flow] whose values are generated with [transform] function by combining
* the most recently emitted values by each flow.
*/
-@ExperimentalCoroutinesApi
+@FlowPreview
public inline fun <T1, T2, T3, T4, T5, R> Flow<T1>.combineLatest(
other: Flow<T2>,
other2: Flow<T3>,
@@ -137,7 +137,7 @@
* Returns a [Flow] whose values are generated with [transform] function by combining
* the most recently emitted values by each flow.
*/
-@ExperimentalCoroutinesApi
+@FlowPreview
public inline fun <reified T, R> Flow<T>.combineLatest(vararg others: Flow<T>, crossinline transform: suspend (Array<T>) -> R): Flow<R> =
combineLatest(*others, arrayFactory = { arrayOfNulls(others.size + 1) }, transform = { transform(it) })
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
index a98b7f3..42ac800 100644
--- a/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
@@ -27,7 +27,6 @@
* .collect() // trigger collection of the flow
* ```
*/
-@ExperimentalCoroutinesApi // tentatively stable in 1.3.0
public suspend fun Flow<*>.collect() = collect(NopCollector)
/**
@@ -69,13 +68,25 @@
* }
* ```
*/
-@ExperimentalCoroutinesApi
public suspend inline fun <T> Flow<T>.collect(crossinline action: suspend (value: T) -> Unit): Unit =
collect(object : FlowCollector<T> {
override suspend fun emit(value: T) = action(value)
})
/**
+ * Terminal flow operator that collects the given flow with a provided [action] that takes the index of an element (zero-based) and the element.
+ * If any exception occurs during collect or in the provided flow, this exception is rethrown from this method.
+ *
+ * See also [collect] and [withIndex].
+ */
+@ExperimentalCoroutinesApi
+public suspend inline fun <T> Flow<T>.collectIndexed(crossinline action: suspend (index: Int, value: T) -> Unit): Unit =
+ collect(object : FlowCollector<T> {
+ private var index = 0
+ override suspend fun emit(value: T) = action(checkIndexOverflow(index++), value)
+ })
+
+/**
* Collects all the values from the given [flow] and emits them to the collector.
*
* It is a shorthand for `flow.collect { value -> emit(value) }`.
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Collection.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Collection.kt
index 836ea7e..e07be61 100644
--- a/kotlinx-coroutines-core/common/src/flow/terminal/Collection.kt
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Collection.kt
@@ -7,25 +7,21 @@
package kotlinx.coroutines.flow
-import kotlinx.coroutines.*
import kotlin.jvm.*
/**
* Collects given flow into a [destination]
*/
-@ExperimentalCoroutinesApi
public suspend fun <T> Flow<T>.toList(destination: MutableList<T> = ArrayList()): List<T> = toCollection(destination)
/**
* Collects given flow into a [destination]
*/
-@ExperimentalCoroutinesApi
public suspend fun <T> Flow<T>.toSet(destination: MutableSet<T> = LinkedHashSet()): Set<T> = toCollection(destination)
/**
* Collects given flow into a [destination]
*/
-@ExperimentalCoroutinesApi
public suspend fun <T, C : MutableCollection<in T>> Flow<T>.toCollection(destination: C): C {
collect { value ->
destination.add(value)
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt
index 1d73700..d57dfde 100644
--- a/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt
@@ -27,7 +27,7 @@
* Returns the number of elements matching the given predicate.
*/
@ExperimentalCoroutinesApi
-public suspend fun <T> Flow<T>.count(predicate: suspend (T) -> Boolean): Int {
+public suspend fun <T> Flow<T>.count(predicate: suspend (T) -> Boolean): Int {
var i = 0
collect { value ->
if (predicate(value)) {
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt
index 8db762e..875e6b6 100644
--- a/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt
@@ -10,7 +10,7 @@
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.internal.*
-import kotlinx.coroutines.flow.unsafeFlow as flow
+import kotlinx.coroutines.flow.internal.unsafeFlow as flow
import kotlin.jvm.*
/**
@@ -55,7 +55,6 @@
* Throws [NoSuchElementException] for empty flow and [IllegalStateException] for flow
* that contains more than one element.
*/
-@ExperimentalCoroutinesApi
public suspend fun <T> Flow<T>.single(): T {
var result: Any? = NULL
collect { value ->
@@ -72,7 +71,6 @@
* The terminal operator, that awaits for one and only one value to be published.
* Throws [IllegalStateException] for flow that contains more than one element.
*/
-@ExperimentalCoroutinesApi
public suspend fun <T: Any> Flow<T>.singleOrNull(): T? {
var result: T? = null
collect { value ->
@@ -87,7 +85,6 @@
* The terminal operator that returns the first element emitted by the flow and then cancels flow's collection.
* Throws [NoSuchElementException] if the flow was empty.
*/
-@ExperimentalCoroutinesApi
public suspend fun <T> Flow<T>.first(): T {
var result: Any? = NULL
try {
@@ -107,7 +104,6 @@
* The terminal operator that returns the first element emitted by the flow matching the given [predicate] and then cancels flow's collection.
* Throws [NoSuchElementException] if the flow has not contained elements matching the [predicate].
*/
-@ExperimentalCoroutinesApi
public suspend fun <T> Flow<T>.first(predicate: suspend (T) -> Boolean): T {
var result: Any? = NULL
try {
diff --git a/kotlinx-coroutines-core/common/src/internal/ArrayCopy.common.kt b/kotlinx-coroutines-core/common/src/internal/ArrayCopy.common.kt
deleted file mode 100644
index ac620ba..0000000
--- a/kotlinx-coroutines-core/common/src/internal/ArrayCopy.common.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.internal
-
-/**
- * Cross-platform array copy. Overlaps of source and destination are not supported
- */
-internal expect fun <E> arraycopy(source: Array<E>, srcPos: Int, destination: Array<E?>, destinationStart: Int, length: Int)
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt b/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt
index 759c0db..61a3233 100644
--- a/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt
+++ b/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt
@@ -36,9 +36,15 @@
val currentSize = elements.size
val newCapacity = currentSize shl 1
val newElements = arrayOfNulls<Any>(newCapacity)
- val remaining = elements.size - head
- arraycopy(elements, head, newElements, 0, remaining)
- arraycopy(elements, 0, newElements, remaining, head)
+ elements.copyInto(
+ destination = newElements,
+ startIndex = head
+ )
+ elements.copyInto(
+ destination = newElements,
+ destinationOffset = elements.size - head,
+ endIndex = head
+ )
elements = newElements
head = 0
tail = currentSize
diff --git a/kotlinx-coroutines-core/common/src/internal/Atomic.kt b/kotlinx-coroutines-core/common/src/internal/Atomic.kt
index b589db2..bc52815 100644
--- a/kotlinx-coroutines-core/common/src/internal/Atomic.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Atomic.kt
@@ -5,6 +5,7 @@
package kotlinx.coroutines.internal
import kotlinx.atomicfu.atomic
+import kotlinx.coroutines.*
/**
* The most abstract operation that can be in process. Other threads observing an instance of this
@@ -40,7 +41,7 @@
val isDecided: Boolean get() = _consensus.value !== NO_DECISION
fun tryDecide(decision: Any?): Boolean {
- check(decision !== NO_DECISION)
+ assert { decision !== NO_DECISION }
return _consensus.compareAndSet(NO_DECISION, decision)
}
diff --git a/kotlinx-coroutines-core/jvm/src/internal/LockFreeTaskQueue.kt b/kotlinx-coroutines-core/common/src/internal/LockFreeTaskQueue.kt
similarity index 92%
rename from kotlinx-coroutines-core/jvm/src/internal/LockFreeTaskQueue.kt
rename to kotlinx-coroutines-core/common/src/internal/LockFreeTaskQueue.kt
index 5855dbb..6872310 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/LockFreeTaskQueue.kt
+++ b/kotlinx-coroutines-core/common/src/internal/LockFreeTaskQueue.kt
@@ -1,12 +1,12 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
import kotlinx.atomicfu.*
-import java.util.*
-import java.util.concurrent.atomic.*
+import kotlinx.coroutines.*
+import kotlin.jvm.*
private typealias Core<E> = LockFreeTaskQueueCore<E>
@@ -83,7 +83,7 @@
private val mask = capacity - 1
private val _next = atomic<Core<E>?>(null)
private val _state = atomic(0L)
- private val array = AtomicReferenceArray<Any?>(capacity)
+ private val array = atomicArrayOfNulls<Any?>(capacity)
init {
check(mask <= MAX_CAPACITY_MASK)
@@ -114,7 +114,7 @@
if ((tail + 2) and mask == head and mask) return ADD_FROZEN // overfull, so do freeze & copy
// If queue is Multi-Consumer then the consumer could still have not cleared element
// despite the above check for one free slot.
- if (!singleConsumer && array[tail and mask] != null) {
+ if (!singleConsumer && array[tail and mask].value != null) {
// There are two options in this situation
// 1. Spin-wait until consumer clears the slot
// 2. Freeze & resize to avoid spinning
@@ -129,7 +129,7 @@
val newTail = (tail + 1) and MAX_CAPACITY_MASK
if (_state.compareAndSet(state, state.updateTail(newTail))) {
// successfully added
- array[tail and mask] = element
+ array[tail and mask].value = element
// could have been frozen & copied before this item was set -- correct it by filling placeholder
var cur = this
while(true) {
@@ -143,7 +143,7 @@
}
private fun fillPlaceholder(index: Int, element: E): Core<E>? {
- val old = array.get(index and mask)
+ val old = array[index and mask].value
/*
* addLast actions:
* 1) Commit tail slot
@@ -155,7 +155,7 @@
* perform *unique* check that current placeholder is our to avoid overwriting another producer placeholder
*/
if (old is Placeholder && old.index == index) {
- array.set(index and mask, element)
+ array[index and mask].value = element
// we've corrected missing element, should check if that propagated to further copies, just in case
return this
}
@@ -172,7 +172,7 @@
if (state and FROZEN_MASK != 0L) return REMOVE_FROZEN // frozen -- cannot modify
state.withState { head, tail ->
if ((tail and mask) == (head and mask)) return null // empty
- val element = array[head and mask]
+ val element = array[head and mask].value
if (element == null) {
// If queue is Single-Consumer, then element == null only when add has not finished yet
if (singleConsumer) return null // consider it not added yet
@@ -189,7 +189,7 @@
if (_state.compareAndSet(state, state.updateHead(newHead))) {
// Array could have been copied by another thread and it is perfectly fine, since only elements
// between head and tail were copied and there are no extra steps we should take here
- array[head and mask] = null // now can safely put null (state was updated)
+ array[head and mask].value = null // now can safely put null (state was updated)
return element // successfully removed in fast-path
}
// Multi-Consumer queue must retry this loop on CAS failure (another consumer might have removed element)
@@ -207,13 +207,13 @@
private fun removeSlowPath(oldHead: Int, newHead: Int): Core<E>? {
_state.loop { state ->
state.withState { head, _ ->
- check(head == oldHead) { "This queue can have only one consumer" }
+ assert { head == oldHead } // "This queue can have only one consumer"
if (state and FROZEN_MASK != 0L) {
// state was already frozen, so removed element was copied to next
return next() // continue to correct head in next
}
if (_state.compareAndSet(state, state.updateHead(newHead))) {
- array[head and mask] = null // now can safely put null (state was updated)
+ array[head and mask].value = null // now can safely put null (state was updated)
return null
}
}
@@ -241,7 +241,8 @@
var index = head
while (index and mask != tail and mask) {
// replace nulls with placeholders on copy
- next.array[index and next.mask] = array[index and mask] ?: Placeholder(index)
+ val value = array[index and mask].value ?: Placeholder(index)
+ next.array[index and next.mask].value = value
index++
}
next._state.value = state wo FROZEN_MASK
@@ -251,12 +252,12 @@
// Used for validation in tests only
fun <R> map(transform: (E) -> R): List<R> {
- val res = ArrayList<R>(array.length())
+ val res = ArrayList<R>(capacity)
_state.value.withState { head, tail ->
var index = head
while (index and mask != tail and mask) {
// replace nulls with placeholders on copy
- val element = array[index and mask]
+ val element = array[index and mask].value
@Suppress("UNCHECKED_CAST")
if (element != null && element !is Placeholder) res.add(transform(element as E))
index++
diff --git a/kotlinx-coroutines-core/common/src/internal/SegmentQueue.kt b/kotlinx-coroutines-core/common/src/internal/SegmentQueue.kt
index 4ad554f..370fcfc 100644
--- a/kotlinx-coroutines-core/common/src/internal/SegmentQueue.kt
+++ b/kotlinx-coroutines-core/common/src/internal/SegmentQueue.kt
@@ -1,8 +1,11 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
package kotlinx.coroutines.internal
-import kotlinx.atomicfu.AtomicRef
-import kotlinx.atomicfu.atomic
-import kotlinx.atomicfu.loop
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
/**
* Essentially, this segment queue is an infinite array of segments, which is represented as
@@ -133,7 +136,7 @@
* logically removed (so [removed] returns `true`) at the point of invocation.
*/
fun remove() {
- check(removed) { " The segment should be logically removed at first "}
+ assert { removed } // The segment should be logically removed at first
// Read `next` and `prev` pointers.
var next = this._next.value ?: return // tail cannot be removed
var prev = prev.value ?: return // head cannot be removed
diff --git a/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt b/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
index 6b05202..8bcc8f0 100644
--- a/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
@@ -4,7 +4,16 @@
package kotlinx.coroutines.internal
-internal expect open class SynchronizedObject() // marker abstract class
+import kotlinx.coroutines.*
-@PublishedApi
-internal expect inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T
\ No newline at end of file
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public expect open class SynchronizedObject() // marker abstract class
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public expect inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.common.kt b/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.common.kt
new file mode 100644
index 0000000..fa8e051
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/ThreadSafeHeap.common.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public interface ThreadSafeHeapNode {
+ public var heap: ThreadSafeHeap<*>?
+ public var index: Int
+}
+
+/**
+ * Synchronized binary heap.
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public open class ThreadSafeHeap<T> : SynchronizedObject() where T: ThreadSafeHeapNode, T: Comparable<T> {
+ private var a: Array<T?>? = null
+
+ private val _size = atomic(0)
+
+ public var size: Int
+ get() = _size.value
+ private set(value) { _size.value = value }
+
+ public val isEmpty: Boolean get() = size == 0
+
+ public fun clear() = synchronized(this) {
+ a?.let { clear(it) }
+ _size.value = 0
+ }
+
+ public fun peek(): T? = synchronized(this) { firstImpl() }
+
+ public fun removeFirstOrNull(): T? = synchronized(this) {
+ if (size > 0) {
+ removeAtImpl(0)
+ } else {
+ null
+ }
+ }
+
+ // @Synchronized // NOTE! NOTE! NOTE! inline fun cannot be @Synchronized
+ public inline fun removeFirstIf(predicate: (T) -> Boolean): T? = synchronized(this) {
+ val first = firstImpl() ?: return null
+ if (predicate(first)) {
+ removeAtImpl(0)
+ } else {
+ null
+ }
+ }
+
+ public fun addLast(node: T) = synchronized(this) { addImpl(node) }
+
+ // @Synchronized // NOTE! NOTE! NOTE! inline fun cannot be @Synchronized
+ // Condition also receives current first node in the heap
+ public inline fun addLastIf(node: T, cond: (T?) -> Boolean): Boolean = synchronized(this) {
+ if (cond(firstImpl())) {
+ addImpl(node)
+ true
+ } else {
+ false
+ }
+ }
+
+ public fun remove(node: T): Boolean = synchronized(this) {
+ return if (node.heap == null) {
+ false
+ } else {
+ val index = node.index
+ assert { index >= 0 }
+ removeAtImpl(index)
+ true
+ }
+ }
+
+ @PublishedApi
+ internal fun firstImpl(): T? = a?.get(0)
+
+ @PublishedApi
+ internal fun removeAtImpl(index: Int): T {
+ assert { size > 0 }
+ val a = this.a!!
+ size--
+ if (index < size) {
+ swap(index, size)
+ val j = (index - 1) / 2
+ if (index > 0 && a[index]!! < a[j]!!) {
+ swap(index, j)
+ siftUpFrom(j)
+ } else {
+ siftDownFrom(index)
+ }
+ }
+ val result = a[size]!!
+ assert { result.heap === this }
+ result.heap = null
+ result.index = -1
+ a[size] = null
+ return result
+ }
+
+ @PublishedApi
+ internal fun addImpl(node: T) {
+ assert { node.heap == null }
+ node.heap = this
+ val a = realloc()
+ val i = size++
+ a[i] = node
+ node.index = i
+ siftUpFrom(i)
+ }
+
+ private tailrec fun siftUpFrom(i: Int) {
+ if (i <= 0) return
+ val a = a!!
+ val j = (i - 1) / 2
+ if (a[j]!! <= a[i]!!) return
+ swap(i, j)
+ siftUpFrom(j)
+ }
+
+ private tailrec fun siftDownFrom(i: Int) {
+ var j = 2 * i + 1
+ if (j >= size) return
+ val a = a!!
+ if (j + 1 < size && a[j + 1]!! < a[j]!!) j++
+ if (a[i]!! <= a[j]!!) return
+ swap(i, j)
+ siftDownFrom(j)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun realloc(): Array<T?> {
+ val a = this.a
+ return when {
+ a == null -> (arrayOfNulls<ThreadSafeHeapNode>(4) as Array<T?>).also { this.a = it }
+ size >= a.size -> a.copyOf(size * 2).also { this.a = it }
+ else -> a
+ }
+ }
+
+ private fun swap(i: Int, j: Int) {
+ val a = a!!
+ val ni = a[j]!!
+ val nj = a[i]!!
+ a[i] = ni
+ a[j] = nj
+ ni.index = i
+ nj.index = j
+ }
+}
+
+internal expect fun <T> clear(a: Array<T?>)
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt
index 9d71b7a..b42fde3 100644
--- a/kotlinx-coroutines-core/common/src/selects/Select.kt
+++ b/kotlinx-coroutines-core/common/src/selects/Select.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.selects
@@ -234,7 +234,7 @@
override val completion: Continuation<R> get() = this
private inline fun doResume(value: () -> Any?, block: () -> Unit) {
- check(isSelected) { "Must be selected first" }
+ assert { isSelected } // "Must be selected first"
_result.loop { result ->
when {
result === UNDECIDED -> if (_result.compareAndSet(UNDECIDED, value())) return
@@ -343,7 +343,7 @@
// it is just like start(), but support idempotent start
override fun trySelect(idempotent: Any?): Boolean {
- check(idempotent !is OpDescriptor) { "cannot use OpDescriptor as idempotent marker"}
+ assert { idempotent !is OpDescriptor } // "cannot use OpDescriptor as idempotent marker"
while (true) { // lock-free loop on state
val state = this.state
when {
diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt
index b58885c..fa198e1 100644
--- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt
+++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.sync
@@ -206,7 +206,7 @@
is LockedQueue -> {
val curOwner = state.owner
check(curOwner !== owner) { "Already locked by $owner" }
- if (state.addLastIf(waiter, { _state.value === state })) {
+ if (state.addLastIf(waiter) { _state.value === state }) {
// added to waiter list!
cont.removeOnCancellation(waiter)
return@sc
@@ -226,8 +226,7 @@
override fun <R> registerSelectClause2(select: SelectInstance<R>, owner: Any?, block: suspend (Mutex) -> R) {
while (true) { // lock-free loop on state
if (select.isSelected) return
- val state = _state.value
- when (state) {
+ when (val state = _state.value) {
is Empty -> {
if (state.locked !== UNLOCKED) { // try upgrade to queue & retry
_state.compareAndSet(state, LockedQueue(state.locked))
@@ -388,7 +387,7 @@
) : LockWaiter(owner) {
override fun tryResumeLockWaiter(): Any? = if (select.trySelect(null)) SELECT_SUCCESS else null
override fun completeResumeLockWaiter(token: Any) {
- check(token === SELECT_SUCCESS)
+ assert { token === SELECT_SUCCESS }
block.startCoroutine(receiver = mutex, completion = select.completion)
}
override fun toString(): String = "LockSelect[$owner, $mutex, $select]"
diff --git a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt
index 0ffb990..6e0552d 100644
--- a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt
+++ b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt
@@ -121,14 +121,16 @@
}
override fun release() {
- val p = _availablePermits.getAndUpdate { cur ->
- check(cur < permits) { "The number of acquired permits cannot be greater than `permits`" }
- cur + 1
- }
+ val p = incPermits()
if (p >= 0) return // no waiters
resumeNextFromQueue()
}
+ internal fun incPermits() = _availablePermits.getAndUpdate { cur ->
+ check(cur < permits) { "The number of acquired permits cannot be greater than `permits`" }
+ cur + 1
+ }
+
private suspend fun addToQueueAndSuspend() = suspendAtomicCancellableCoroutine<Unit> sc@ { cont ->
val last = this.tail
val enqIdx = enqIdx.getAndIncrement()
@@ -143,37 +145,37 @@
}
@Suppress("UNCHECKED_CAST")
- private fun resumeNextFromQueue() {
- val first = this.head
- val deqIdx = deqIdx.getAndIncrement()
- val segment = getSegmentAndMoveHead(first, deqIdx / SEGMENT_SIZE) ?: return
- val i = (deqIdx % SEGMENT_SIZE).toInt()
- val cont = segment.getAndUpdate(i) {
- // Cancelled continuation invokes `release`
- // and resumes next suspended acquirer if needed.
- if (it === CANCELLED) return
- RESUMED
+ internal fun resumeNextFromQueue() {
+ try_again@while (true) {
+ val first = this.head
+ val deqIdx = deqIdx.getAndIncrement()
+ val segment = getSegmentAndMoveHead(first, deqIdx / SEGMENT_SIZE) ?: continue@try_again
+ val i = (deqIdx % SEGMENT_SIZE).toInt()
+ val cont = segment.getAndSet(i, RESUMED)
+ if (cont === null) return // just resumed
+ if (cont === CANCELLED) continue@try_again
+ (cont as CancellableContinuation<Unit>).resume(Unit)
+ return
}
- if (cont === null) return // just resumed
- (cont as CancellableContinuation<Unit>).resume(Unit)
}
}
private class CancelSemaphoreAcquisitionHandler(
- private val semaphore: Semaphore,
+ private val semaphore: SemaphoreImpl,
private val segment: SemaphoreSegment,
private val index: Int
) : CancelHandler() {
override fun invoke(cause: Throwable?) {
- segment.cancel(index)
- semaphore.release()
+ semaphore.incPermits()
+ if (segment.cancel(index)) return
+ semaphore.resumeNextFromQueue()
}
override fun toString() = "CancelSemaphoreAcquisitionHandler[$semaphore, $segment, $index]"
}
private class SemaphoreSegment(id: Long, prev: SemaphoreSegment?): Segment<SemaphoreSegment>(id, prev) {
- private val acquirers = atomicArrayOfNulls<Any?>(SEGMENT_SIZE)
+ val acquirers = atomicArrayOfNulls<Any?>(SEGMENT_SIZE)
@Suppress("NOTHING_TO_INLINE")
inline fun get(index: Int): Any? = acquirers[index].value
@@ -181,25 +183,21 @@
@Suppress("NOTHING_TO_INLINE")
inline fun cas(index: Int, expected: Any?, value: Any?): Boolean = acquirers[index].compareAndSet(expected, value)
- inline fun getAndUpdate(index: Int, function: (Any?) -> Any?): Any? {
- while (true) {
- val cur = acquirers[index].value
- val upd = function(cur)
- if (cas(index, cur, upd)) return cur
- }
- }
+ @Suppress("NOTHING_TO_INLINE")
+ inline fun getAndSet(index: Int, value: Any?) = acquirers[index].getAndSet(value)
private val cancelledSlots = atomic(0)
override val removed get() = cancelledSlots.value == SEGMENT_SIZE
// Cleans the acquirer slot located by the specified index
// and removes this segment physically if all slots are cleaned.
- fun cancel(index: Int) {
- // Clean the specified waiter
- acquirers[index].value = CANCELLED
+ fun cancel(index: Int): Boolean {
+ // Try to cancel the slot
+ val cancelled = getAndSet(index, CANCELLED) !== RESUMED
// Remove this segment if needed
if (cancelledSlots.incrementAndGet() == SEGMENT_SIZE)
remove()
+ return cancelled
}
override fun toString() = "SemaphoreSegment[id=$id, hashCode=${hashCode()}]"
diff --git a/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt b/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt
index 167edba..a6ddd81 100644
--- a/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt
@@ -35,6 +35,11 @@
}
@Test
+ fun testReceiveOrClosed() = runTest {
+ TestChannelKind.values().forEach { kind -> testReceiveOrClosed(kind) }
+ }
+
+ @Test
fun testInvokeOnClose() = TestChannelKind.values().forEach { kind ->
reset()
val channel = kind.create()
@@ -124,6 +129,34 @@
assertTrue(d.getCancellationException().cause is TestException)
}
+ @Suppress("ReplaceAssertBooleanWithAssertEquality")
+ private suspend fun testReceiveOrClosed(kind: TestChannelKind) = coroutineScope {
+ reset()
+ val channel = kind.create()
+ launch {
+ expect(2)
+ channel.send(1)
+ }
+
+ expect(1)
+ val result = channel.receiveOrClosed()
+ assertEquals(1, result.value)
+ assertEquals(1, result.valueOrNull)
+ assertTrue(ValueOrClosed.value(1) == result)
+
+ expect(3)
+ launch {
+ expect(4)
+ channel.close()
+ }
+ val closed = channel.receiveOrClosed()
+ expect(5)
+ assertNull(closed.valueOrNull)
+ assertTrue(closed.isClosed)
+ assertNull(closed.closeCause)
+ assertTrue(ValueOrClosed.closed<Int>(closed.closeCause) == closed)
+ finish(6)
+ }
private suspend fun testOffer(kind: TestChannelKind) = coroutineScope {
val channel = kind.create()
diff --git a/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt b/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt
index cbaf708..bf85c74 100644
--- a/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt
@@ -145,7 +145,7 @@
fun testAwaitIllegalState() = runTest {
val channel = produce<Int> { }
@Suppress("RemoveExplicitTypeArguments") // KT-31525
- assertFailsWith<IllegalStateException> { (channel as ProducerScope<*>).awaitClose<Nothing>() }
+ assertFailsWith<IllegalStateException> { (channel as ProducerScope<*>).awaitClose() }
}
private suspend fun cancelOnCompletion(coroutineContext: CoroutineContext) = CoroutineScope(coroutineContext).apply {
diff --git a/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt b/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt
index 465699e..27c5816 100644
--- a/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt
+++ b/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt
@@ -58,6 +58,7 @@
override suspend fun receive(): E = sub.receive()
override suspend fun receiveOrNull(): E? = sub.receiveOrNull()
+ override suspend fun receiveOrClosed(): ValueOrClosed<E> = sub.receiveOrClosed()
override fun poll(): E? = sub.poll()
override fun iterator(): ChannelIterator<E> = sub.iterator()
@@ -71,4 +72,6 @@
get() = sub.onReceive
override val onReceiveOrNull: SelectClause1<E?>
get() = sub.onReceiveOrNull
+ override val onReceiveOrClosed: SelectClause1<ValueOrClosed<E>>
+ get() = sub.onReceiveOrClosed
}
diff --git a/kotlinx-coroutines-core/common/test/flow/NamedDispatchers.kt b/kotlinx-coroutines-core/common/test/flow/NamedDispatchers.kt
index 5e48bb6..67bcbdc 100644
--- a/kotlinx-coroutines-core/common/test/flow/NamedDispatchers.kt
+++ b/kotlinx-coroutines-core/common/test/flow/NamedDispatchers.kt
@@ -4,7 +4,6 @@
package kotlinx.coroutines
-import kotlinx.coroutines.internal.*
import kotlin.coroutines.*
import kotlin.native.concurrent.*
@@ -55,9 +54,15 @@
val currentSize = elements.size
val newCapacity = currentSize shl 1
val newElements = arrayOfNulls<String>(newCapacity)
- val remaining = elements.size - head
- arraycopy(elements, head, newElements, 0, remaining)
- arraycopy(elements, 0, newElements, remaining, head)
+ elements.copyInto(
+ destination = newElements,
+ startIndex = head
+ )
+ elements.copyInto(
+ destination = newElements,
+ destinationOffset = elements.size - head,
+ endIndex = head
+ )
elements = newElements
}
}
diff --git a/kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt
index 3c74b0f..de5c220 100644
--- a/kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/channels/ChannelBuildersFlowTest.kt
@@ -10,6 +10,76 @@
class ChannelBuildersFlowTest : TestBase() {
@Test
+ fun testChannelConsumeAsFlow() = runTest {
+ val channel = produce {
+ repeat(10) {
+ send(it + 1)
+ }
+ }
+ val flow = channel.consumeAsFlow()
+ assertEquals(55, flow.sum())
+ assertFailsWith<IllegalStateException> { flow.collect() }
+ }
+
+ @Test
+ fun testConsumeAsFlowCancellation() = runTest {
+ val channel = produce(NonCancellable) { // otherwise failure will cancel scope as well
+ repeat(10) {
+ send(it + 1)
+ }
+ throw TestException()
+ }
+ val flow = channel.consumeAsFlow()
+ assertEquals(15, flow.take(5).sum())
+ // the channel should have been canceled, even though took only 5 elements
+ assertTrue(channel.isClosedForReceive)
+ assertFailsWith<IllegalStateException> { flow.collect() }
+ }
+
+ @Test
+ fun testConsumeAsFlowException() = runTest {
+ val channel = produce(NonCancellable) { // otherwise failure will cancel scope as well
+ repeat(10) {
+ send(it + 1)
+ }
+ throw TestException()
+ }
+ val flow = channel.consumeAsFlow()
+ assertFailsWith<TestException> { flow.sum() }
+ assertFailsWith<IllegalStateException> { flow.collect() }
+ }
+
+ @Test
+ fun testConsumeAsFlowProduceFusing() = runTest {
+ val channel = produce { send("OK") }
+ val flow = channel.consumeAsFlow()
+ assertSame(channel, flow.produceIn(this))
+ assertFailsWith<IllegalStateException> { flow.produceIn(this) }
+ channel.cancel()
+ }
+
+ @Test
+ fun testConsumeAsFlowProduceBuffered() = runTest {
+ expect(1)
+ val channel = produce {
+ expect(3)
+ (1..10).forEach { send(it) }
+ expect(4) // produces everything because of buffering
+ }
+ val flow = channel.consumeAsFlow().buffer() // request buffering
+ expect(2) // producer is not running yet
+ val result = flow.produceIn(this)
+ // run the flow pipeline until it consumes everything into buffer
+ while (!channel.isClosedForReceive) yield()
+ expect(5) // produced had done running (buffered stuff)
+ assertNotSame(channel, result)
+ assertFailsWith<IllegalStateException> { flow.produceIn(this) }
+ // check that we received everything
+ assertEquals((1..10).toList(), result.toList())
+ finish(6)
+ }
+
+ @Test
fun testBroadcastChannelAsFlow() = runTest {
val channel = broadcast {
repeat(10) {
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/DistinctUntilChangedTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/DistinctUntilChangedTest.kt
index cb4fb83..fc03d36 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/DistinctUntilChangedTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/DistinctUntilChangedTest.kt
@@ -33,6 +33,28 @@
}
@Test
+ fun testDistinctUntilChangedAreEquivalent() = runTest {
+ val flow = flow {
+ emit(Box(1))
+ emit(Box(1))
+ emit(Box(2))
+ emit(Box(1))
+ }
+
+ val sum1 = flow.distinctUntilChanged().map { it.i }.sum()
+ val sum2 = flow.distinctUntilChanged { old, new -> old.i == new.i }.map { it.i }.sum()
+ assertEquals(5, sum1)
+ assertEquals(4, sum2)
+ }
+
+ @Test
+ fun testDistinctUntilChangedAreEquivalentSingleValue() = runTest {
+ val flow = flowOf(1)
+ val values = flow.distinctUntilChanged { _, _ -> fail("Expected not to compare single value.") }.toList()
+ assertEquals(listOf(1), values)
+ }
+
+ @Test
fun testThrowingKeySelector() = runTest {
val flow = flow {
coroutineScope {
@@ -50,8 +72,26 @@
}
@Test
- fun testDistinctUntilChangedNull() = runTest{
- val flow = flowOf(null, 1, null).distinctUntilChanged()
+ fun testThrowingAreEquivalent() = runTest {
+ val flow = flow {
+ coroutineScope {
+ launch(start = CoroutineStart.ATOMIC) {
+ hang { expect(3) }
+ }
+ expect(2)
+ emit(1)
+ emit(2)
+ }
+ }.distinctUntilChanged { _, _ -> throw TestException() }
+
+ expect(1)
+ assertFailsWith<TestException>(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testDistinctUntilChangedNull() = runTest {
+ val flow = flowOf(null, 1, null, null).distinctUntilChanged()
assertEquals(listOf(null, 1, null), flow.toList())
}
}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/IndexedTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/IndexedTest.kt
new file mode 100644
index 0000000..53db88d
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/IndexedTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class IndexedTest : TestBase() {
+
+ @Test
+ fun testWithIndex() = runTest {
+ val flow = flowOf(3, 2, 1).withIndex()
+ assertEquals(listOf(IndexedValue(0, 3), IndexedValue(1, 2), IndexedValue(2, 1)), flow.toList())
+ }
+
+ @Test
+ fun testWithIndexEmpty() = runTest {
+ val flow = emptyFlow<Int>().withIndex()
+ assertEquals(emptyList(), flow.toList())
+ }
+
+ @Test
+ fun testCollectIndexed() = runTest {
+ val result = ArrayList<IndexedValue<Long>>()
+ flowOf(3L, 2L, 1L).collectIndexed { index, value ->
+ result.add(IndexedValue(index, value))
+ }
+ assertEquals(listOf(IndexedValue(0, 3L), IndexedValue(1, 2L), IndexedValue(2, 1L)), result)
+ }
+
+ @Test
+ fun testCollectIndexedEmptyFlow() = runTest {
+ val flow = flow<Int> {
+ expect(1)
+ }
+
+ flow.collectIndexed { _, _ ->
+ expectUnreached()
+ }
+
+ finish(2)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt
index ff29481..af50608 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/OnCompletionTest.kt
@@ -112,4 +112,73 @@
}.collect()
finish(4)
}
+
+ @Test
+ fun testEmitExample() = runTest {
+ val flow = flowOf("a", "b", "c")
+ .onCompletion() { emit("Done") }
+ assertEquals(listOf("a", "b", "c", "Done"), flow.toList())
+ }
+
+ sealed class TestData {
+ data class Value(val i: Int) : TestData()
+ data class Done(val e: Throwable?) : TestData() {
+ override fun equals(other: Any?): Boolean =
+ other is Done && other.e?.message == e?.message
+ }
+ }
+
+ @Test
+ fun testCrashedEmit() = runTest {
+ expect(1)
+ val collected = ArrayList<TestData>()
+ assertFailsWith<TestException> {
+ (1..10).asFlow()
+ .map<Int, TestData> { TestData.Value(it) }
+ .onEach { value ->
+ value as TestData.Value
+ expect(value.i + 1)
+ if (value.i == 6) throw TestException("OK")
+ yield()
+ }
+ .onCompletion { e ->
+ expect(8)
+ assertTrue(e is TestException)
+ emit(TestData.Done(e))
+ }.collect {
+ collected += it
+ }
+ }
+ val expected = (1..5).map { TestData.Value(it) } + TestData.Done(TestException("OK"))
+ assertEquals(expected, collected)
+ finish(9)
+ }
+
+ @Test
+ fun testCancelledEmit() = runTest {
+ expect(1)
+ val collected = ArrayList<TestData>()
+ assertFailsWith<JobCancellationException> {
+ coroutineScope {
+ (1..10).asFlow()
+ .map<Int, TestData> { TestData.Value(it) }
+ .onEach { value ->
+ value as TestData.Value
+ expect(value.i + 1)
+ if (value.i == 6) coroutineContext.cancel()
+ yield()
+ }
+ .onCompletion { e ->
+ expect(8)
+ assertNull(e)
+ emit(TestData.Done(e))
+ }.collect {
+ collected += it
+ }
+ }
+ }
+ val expected = (1..5).map { TestData.Value(it) } + TestData.Done(null)
+ assertEquals(expected, collected)
+ finish(9)
+ }
}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/OnStartTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/OnStartTest.kt
new file mode 100644
index 0000000..a0981ab
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/OnStartTest.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class OnStartTest : TestBase() {
+ @Test
+ fun testEmitExample() = runTest {
+ val flow = flowOf("a", "b", "c")
+ .onStart { emit("Begin") }
+ assertEquals(listOf("Begin", "a", "b", "c"), flow.toList())
+ }
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/TransformTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/TransformTest.kt
new file mode 100644
index 0000000..feb3596
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/TransformTest.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlin.test.*
+
+class TransformTest : TestBase() {
+ @Test
+ fun testDoubleEmit() = runTest {
+ val flow = flowOf(1, 2, 3)
+ .transform {
+ emit(it)
+ emit(it)
+ }
+ assertEquals(listOf(1, 1, 2, 2, 3, 3), flow.toList())
+ }
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/SingleTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/SingleTest.kt
index 5ce6d47..f720595 100644
--- a/kotlinx-coroutines-core/common/test/flow/terminal/SingleTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/SingleTest.kt
@@ -7,7 +7,7 @@
import kotlinx.coroutines.*
import kotlin.test.*
-class SingleTest : TestBase() {
+class SingleTest : TestBase() {
@Test
fun testSingle() = runTest {
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt
index f8c3439..ece95db 100644
--- a/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt
@@ -284,6 +284,104 @@
finish(10)
}
+ @Test
+ fun testSelectReceiveOrClosedWaitClosed() = runTest {
+ expect(1)
+ val channel = Channel<String>(1)
+ launch {
+ expect(3)
+ channel.close()
+ expect(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onReceiveOrClosed {
+ expect(5)
+ assertTrue(it.isClosed)
+ assertNull(it.closeCause)
+ }
+ }
+
+ finish(6)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosedWaitClosedWithCause() = runTest {
+ expect(1)
+ val channel = Channel<String>(1)
+ launch {
+ expect(3)
+ channel.close(TestException())
+ expect(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onReceiveOrClosed {
+ expect(5)
+ assertTrue(it.isClosed)
+ assertTrue(it.closeCause is TestException)
+ }
+ }
+
+ finish(6)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosed() = runTest {
+ val c = Channel<Int>(1)
+ val iterations = 10
+ expect(1)
+ val job = launch {
+ repeat(iterations) {
+ select<Unit> {
+ c.onReceiveOrClosed { v ->
+ expect(4 + it * 2)
+ assertEquals(it, v.value)
+ }
+ }
+ }
+ }
+
+ expect(2)
+ repeat(iterations) {
+ expect(3 + it * 2)
+ c.send(it)
+ yield()
+ }
+
+ job.join()
+ finish(3 + iterations * 2)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosedDispatch() = runTest {
+ val c = Channel<Int>(1)
+ expect(1)
+ launch {
+ expect(3)
+ val res = select<String> {
+ c.onReceiveOrClosed { v ->
+ expect(6)
+ assertEquals(42, v.value)
+ yield() // back to main
+ expect(8)
+ "OK"
+ }
+ }
+ expect(9)
+ assertEquals("OK", res)
+ }
+ expect(2)
+ yield() // to launch
+ expect(4)
+ c.send(42) // do not suspend
+ expect(5)
+ yield() // to receive
+ expect(7)
+ yield() // again
+ finish(10)
+ }
+
// only for debugging
internal fun <R> SelectBuilder<R>.default(block: suspend () -> R) {
this as SelectBuilderImpl // type assertion
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt
index 0c1f9f6..ed8b8d3 100644
--- a/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt
@@ -305,6 +305,104 @@
finish(10)
}
+ @Test
+ fun testSelectReceiveOrClosedWaitClosed() = runTest {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ launch {
+ expect(3)
+ channel.close()
+ expect(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onReceiveOrClosed {
+ expect(5)
+ assertTrue(it.isClosed)
+ assertNull(it.closeCause)
+ }
+ }
+
+ finish(6)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosedWaitClosedWithCause() = runTest {
+ expect(1)
+ val channel = Channel<String>(Channel.RENDEZVOUS)
+ launch {
+ expect(3)
+ channel.close(TestException())
+ expect(4)
+ }
+ expect(2)
+ select<Unit> {
+ channel.onReceiveOrClosed {
+ expect(5)
+ assertTrue(it.isClosed)
+ assertTrue(it.closeCause is TestException)
+ }
+ }
+
+ finish(6)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosed() = runTest {
+ val channel = Channel<Int>(Channel.RENDEZVOUS)
+ val iterations = 10
+ expect(1)
+ val job = launch {
+ repeat(iterations) {
+ select<Unit> {
+ channel.onReceiveOrClosed { v ->
+ expect(4 + it * 2)
+ assertEquals(it, v.value)
+ }
+ }
+ }
+ }
+
+ expect(2)
+ repeat(iterations) {
+ expect(3 + it * 2)
+ channel.send(it)
+ yield()
+ }
+
+ job.join()
+ finish(3 + iterations * 2)
+ }
+
+ @Test
+ fun testSelectReceiveOrClosedDispatch() = runTest {
+ val c = Channel<Int>(Channel.RENDEZVOUS)
+ expect(1)
+ launch {
+ expect(3)
+ val res = select<String> {
+ c.onReceiveOrClosed { v ->
+ expect(6)
+ assertEquals(42, v.value)
+ yield() // back to main
+ expect(8)
+ "OK"
+ }
+ }
+ expect(9)
+ assertEquals("OK", res)
+ }
+ expect(2)
+ yield() // to launch
+ expect(4)
+ c.send(42) // do not suspend
+ expect(5)
+ yield() // to receive
+ expect(7)
+ yield() // again
+ finish(10)
+ }
+
// only for debugging
internal fun <R> SelectBuilder<R>.default(block: suspend () -> R) {
this as SelectBuilderImpl // type assertion
diff --git a/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt b/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt
index a6aaf24..dc14a12 100644
--- a/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt
+++ b/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt
@@ -102,7 +102,7 @@
}
@Test
- fun testCancellationReleasesSemaphore() = runTest {
+ fun testCancellationReturnsPermitBack() = runTest {
val semaphore = Semaphore(1)
semaphore.acquire()
assertEquals(0, semaphore.availablePermits)
@@ -116,4 +116,28 @@
semaphore.release()
assertEquals(1, semaphore.availablePermits)
}
+
+ @Test
+ fun testCancellationDoesNotResumeWaitingAcquirers() = runTest {
+ val semaphore = Semaphore(1)
+ semaphore.acquire()
+ val job1 = launch { // 1st job in the waiting queue
+ expect(2)
+ semaphore.acquire()
+ expectUnreached()
+ }
+ val job2 = launch { // 2nd job in the waiting queue
+ expect(3)
+ semaphore.acquire()
+ expectUnreached()
+ }
+ expect(1)
+ yield()
+ expect(4)
+ job2.cancel()
+ yield()
+ expect(5)
+ job1.cancel()
+ finish(6)
+ }
}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/src/Debug.kt b/kotlinx-coroutines-core/js/src/Debug.kt
index 143cbb6..57a94d4 100644
--- a/kotlinx-coroutines-core/js/src/Debug.kt
+++ b/kotlinx-coroutines-core/js/src/Debug.kt
@@ -1,11 +1,13 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
private var counter = 0
+internal actual val DEBUG: Boolean = false
+
internal actual val Any.hexAddress: String
get() {
var result = this.asDynamic().__debug_counter
@@ -18,3 +20,5 @@
}
internal actual val Any.classSimpleName: String get() = this::class.simpleName ?: "Unknown"
+
+internal actual inline fun assert(value: () -> Boolean) {}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/src/EventLoop.kt b/kotlinx-coroutines-core/js/src/EventLoop.kt
index 7a27fe6..19d75c0 100644
--- a/kotlinx-coroutines-core/js/src/EventLoop.kt
+++ b/kotlinx-coroutines-core/js/src/EventLoop.kt
@@ -8,8 +8,20 @@
internal actual fun createEventLoop(): EventLoop = UnconfinedEventLoop()
+internal actual fun nanoTime(): Long = unsupported()
+
internal class UnconfinedEventLoop : EventLoop() {
- override fun dispatch(context: CoroutineContext, block: Runnable) {
- throw UnsupportedOperationException("runBlocking event loop is not supported")
- }
+ override fun dispatch(context: CoroutineContext, block: Runnable): Unit = unsupported()
}
+
+internal actual abstract class EventLoopImplPlatform : EventLoop() {
+ protected actual fun unpark(): Unit = unsupported()
+ protected actual fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask): Unit = unsupported()
+}
+
+internal actual object DefaultExecutor {
+ public actual fun enqueue(task: Runnable): Unit = unsupported()
+}
+
+private fun unsupported(): Nothing =
+ throw UnsupportedOperationException("runBlocking event loop is not supported")
diff --git a/kotlinx-coroutines-core/js/src/internal/ArrayCopy.kt b/kotlinx-coroutines-core/js/src/internal/ArrayCopy.kt
deleted file mode 100644
index c6bd1aa..0000000
--- a/kotlinx-coroutines-core/js/src/internal/ArrayCopy.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.internal
-
-internal actual fun <E> arraycopy(source: Array<E>, srcPos: Int, destination: Array<E?>, destinationStart: Int, length: Int) {
- var destinationIndex = destinationStart
- for (sourceIndex in srcPos until srcPos + length) {
- destination[destinationIndex++] = source[sourceIndex]
- }
-}
diff --git a/kotlinx-coroutines-core/js/src/internal/Synchronized.kt b/kotlinx-coroutines-core/js/src/internal/Synchronized.kt
index 1c12140..fbed546 100644
--- a/kotlinx-coroutines-core/js/src/internal/Synchronized.kt
+++ b/kotlinx-coroutines-core/js/src/internal/Synchronized.kt
@@ -4,9 +4,17 @@
package kotlinx.coroutines.internal
-@Suppress("ACTUAL_WITHOUT_EXPECT") // visibility
-internal actual typealias SynchronizedObject = Any
+import kotlinx.coroutines.*
-@PublishedApi
-internal actual inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T =
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual typealias SynchronizedObject = Any
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T =
block()
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/src/internal/ThreadSafeHeap.kt b/kotlinx-coroutines-core/js/src/internal/ThreadSafeHeap.kt
new file mode 100644
index 0000000..f966c999
--- /dev/null
+++ b/kotlinx-coroutines-core/js/src/internal/ThreadSafeHeap.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun <T> clear(a: Array<T?>) {
+ for (i in a.indices) a[i] = null
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/js/test/internal/ArrayCopyKtTest.kt b/kotlinx-coroutines-core/js/test/internal/ArrayCopyKtTest.kt
deleted file mode 100644
index 42bc5ae..0000000
--- a/kotlinx-coroutines-core/js/test/internal/ArrayCopyKtTest.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.internal
-
-import kotlin.test.*
-
-class ArrayCopyTest {
-
- @Test
- fun testArrayCopy() {
- val source = Array(10, { it })
- val destination = arrayOfNulls<Int>(7)
- arraycopy(source, 2, destination, 1, 5)
- assertEquals(listOf(null, 2, 3, 4, 5, 6, null), destination.toList())
- }
-}
diff --git a/kotlinx-coroutines-core/jvm/src/Builders.kt b/kotlinx-coroutines-core/jvm/src/Builders.kt
index 52841cd..ac3cade 100644
--- a/kotlinx-coroutines-core/jvm/src/Builders.kt
+++ b/kotlinx-coroutines-core/jvm/src/Builders.kt
@@ -69,7 +69,7 @@
@Suppress("UNCHECKED_CAST")
fun joinBlocking(): T {
- timeSource.registerTimeLoopThread()
+ registerTimeLoopThread()
try {
eventLoop?.incrementUseCount()
try {
@@ -79,13 +79,13 @@
val parkNanos = eventLoop?.processNextEvent() ?: Long.MAX_VALUE
// note: process next even may loose unpark flag, so check if completed before parking
if (isCompleted) break
- timeSource.parkNanos(this, parkNanos)
+ parkNanos(this, parkNanos)
}
} finally { // paranoia
eventLoop?.decrementUseCount()
}
} finally { // paranoia
- timeSource.unregisterTimeLoopThread()
+ unregisterTimeLoopThread()
}
// now return result
val state = this.state.unboxState()
diff --git a/kotlinx-coroutines-core/jvm/src/CommonPool.kt b/kotlinx-coroutines-core/jvm/src/CommonPool.kt
index 7c8ac7c..1b5aae9 100644
--- a/kotlinx-coroutines-core/jvm/src/CommonPool.kt
+++ b/kotlinx-coroutines-core/jvm/src/CommonPool.kt
@@ -100,9 +100,9 @@
override fun dispatch(context: CoroutineContext, block: Runnable) {
try {
- (pool ?: getOrCreatePoolSync()).execute(timeSource.wrapTask(block))
+ (pool ?: getOrCreatePoolSync()).execute(wrapTask(block))
} catch (e: RejectedExecutionException) {
- timeSource.unTrackTask()
+ unTrackTask()
DefaultExecutor.enqueue(block)
}
}
diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
index bd586d6..1d0c4d6 100644
--- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
+++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
@@ -9,13 +9,6 @@
import java.util.concurrent.atomic.*
import kotlin.coroutines.*
-private val COROUTINE_ID = AtomicLong()
-
-// for tests only
-internal fun resetCoroutineId() {
- COROUTINE_ID.set(0)
-}
-
internal const val COROUTINES_SCHEDULER_PROPERTY_NAME = "kotlinx.coroutines.scheduler"
internal val useCoroutinesScheduler = systemProp(COROUTINES_SCHEDULER_PROPERTY_NAME).let { value ->
diff --git a/kotlinx-coroutines-core/jvm/src/Debug.kt b/kotlinx-coroutines-core/jvm/src/Debug.kt
index 3c750da..40de02a 100644
--- a/kotlinx-coroutines-core/jvm/src/Debug.kt
+++ b/kotlinx-coroutines-core/jvm/src/Debug.kt
@@ -1,11 +1,15 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+// Need InlineOnly for efficient bytecode on Android
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
package kotlinx.coroutines
import kotlinx.coroutines.internal.*
-import kotlin.coroutines.*
+import java.util.concurrent.atomic.*
+import kotlin.internal.InlineOnly
/**
* Name of the property that controls coroutine debugging. See [newCoroutineContext][CoroutineScope.newCoroutineContext].
@@ -68,10 +72,13 @@
*/
public const val DEBUG_PROPERTY_VALUE_OFF = "off"
-@JvmField
-internal val DEBUG = systemProp(DEBUG_PROPERTY_NAME).let { value ->
+// @JvmField: Don't use JvmField here to enable R8 optimizations via "assumenosideeffects"
+internal val ASSERTIONS_ENABLED = CoroutineId::class.java.desiredAssertionStatus()
+
+// @JvmField: Don't use JvmField here to enable R8 optimizations via "assumenosideeffects"
+internal actual val DEBUG = systemProp(DEBUG_PROPERTY_NAME).let { value ->
when (value) {
- DEBUG_PROPERTY_VALUE_AUTO, null -> CoroutineId::class.java.desiredAssertionStatus()
+ DEBUG_PROPERTY_VALUE_AUTO, null -> ASSERTIONS_ENABLED
DEBUG_PROPERTY_VALUE_ON, "" -> true
DEBUG_PROPERTY_VALUE_OFF -> false
else -> error("System property '$DEBUG_PROPERTY_NAME' has unrecognized value '$value'")
@@ -79,18 +86,19 @@
}
// Note: stack-trace recovery is enabled only in debug mode
-@JvmField
-internal actual val RECOVER_STACK_TRACES = DEBUG && systemProp(STACKTRACE_RECOVERY_PROPERTY_NAME, true)
+// @JvmField: Don't use JvmField here to enable R8 optimizations via "assumenosideeffects"
+internal actual val RECOVER_STACK_TRACES =
+ DEBUG && systemProp(STACKTRACE_RECOVERY_PROPERTY_NAME, true)
-// internal debugging tools
+// It is used only in debug mode
+internal val COROUTINE_ID = AtomicLong(0)
-internal actual val Any.hexAddress: String
- get() = Integer.toHexString(System.identityHashCode(this))
-
-internal actual fun Continuation<*>.toDebugString(): String = when (this) {
- is DispatchedContinuation -> toString()
- // Workaround for #858
- else -> kotlin.runCatching { "$this@$hexAddress" }.getOrElse { "${this::class.java.name}@$hexAddress" }
+// for tests only
+internal fun resetCoroutineId() {
+ COROUTINE_ID.set(0)
}
-internal actual val Any.classSimpleName: String get() = this::class.java.simpleName
+@InlineOnly
+internal actual inline fun assert(value: () -> Boolean) {
+ if (ASSERTIONS_ENABLED && !value()) throw AssertionError()
+}
diff --git a/kotlinx-coroutines-core/jvm/src/DebugStrings.kt b/kotlinx-coroutines-core/jvm/src/DebugStrings.kt
new file mode 100644
index 0000000..78ad418
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/src/DebugStrings.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+import kotlin.coroutines.*
+
+// internal debugging tools for string representation
+
+internal actual val Any.hexAddress: String
+ get() = Integer.toHexString(System.identityHashCode(this))
+
+internal actual fun Continuation<*>.toDebugString(): String = when (this) {
+ is DispatchedContinuation -> toString()
+ // Workaround for #858
+ else -> runCatching { "$this@$hexAddress" }.getOrElse { "${this::class.java.name}@$hexAddress" }
+}
+
+internal actual val Any.classSimpleName: String get() = this::class.java.simpleName
diff --git a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt
index 8358dcc..19adcef 100644
--- a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt
+++ b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt
@@ -9,7 +9,7 @@
internal actual val DefaultDelay: Delay = DefaultExecutor
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-internal object DefaultExecutor : EventLoopImplBase(), Runnable {
+internal actual object DefaultExecutor : EventLoopImplBase(), Runnable {
const val THREAD_NAME = "kotlinx.coroutines.DefaultExecutor"
init {
@@ -55,11 +55,11 @@
* but it's not exposed as public API.
*/
override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle =
- DelayedRunnableTask(timeMillis, block).also { schedule(it) }
+ scheduleInvokeOnTimeout(timeMillis, block)
override fun run() {
ThreadLocalEventLoop.setEventLoop(this)
- timeSource.registerTimeLoopThread()
+ registerTimeLoopThread()
try {
var shutdownNanos = Long.MAX_VALUE
if (!notifyStartup()) return
@@ -69,7 +69,7 @@
if (parkNanos == Long.MAX_VALUE) {
// nothing to do, initialize shutdown timeout
if (shutdownNanos == Long.MAX_VALUE) {
- val now = timeSource.nanoTime()
+ val now = nanoTime()
if (shutdownNanos == Long.MAX_VALUE) shutdownNanos = now + KEEP_ALIVE_NANOS
val tillShutdown = shutdownNanos - now
if (tillShutdown <= 0) return // shut thread down
@@ -80,13 +80,13 @@
if (parkNanos > 0) {
// check if shutdown was requested and bail out in this case
if (isShutdownRequested) return
- timeSource.parkNanos(this, parkNanos)
+ parkNanos(this, parkNanos)
}
}
} finally {
_thread = null // this thread is dead
acknowledgeShutdownIfNeeded()
- timeSource.unregisterTimeLoopThread()
+ unregisterTimeLoopThread()
// recheck if queues are empty after _thread reference was set to null (!!!)
if (!isEmpty) thread // recreate thread if it is needed
}
@@ -104,8 +104,8 @@
// used for tests
@Synchronized
internal fun ensureStarted() {
- assert(_thread == null) // ensure we are at a clean state
- assert(debugStatus == FRESH || debugStatus == SHUTDOWN_ACK)
+ assert { _thread == null } // ensure we are at a clean state
+ assert { debugStatus == FRESH || debugStatus == SHUTDOWN_ACK }
debugStatus = FRESH
createThreadSync() // create fresh thread
while (debugStatus == FRESH) (this as Object).wait()
@@ -126,7 +126,7 @@
if (!isShutdownRequested) debugStatus = SHUTDOWN_REQ
// loop while there is anything to do immediately or deadline passes
while (debugStatus != SHUTDOWN_ACK && _thread != null) {
- _thread?.let { timeSource.unpark(it) } // wake up thread if present
+ _thread?.let { unpark(it) } // wake up thread if present
val remaining = deadline - System.currentTimeMillis()
if (remaining <= 0) break
(this as Object).wait(timeout)
diff --git a/kotlinx-coroutines-core/jvm/src/EventLoop.kt b/kotlinx-coroutines-core/jvm/src/EventLoop.kt
index 5d214d1..598f424 100644
--- a/kotlinx-coroutines-core/jvm/src/EventLoop.kt
+++ b/kotlinx-coroutines-core/jvm/src/EventLoop.kt
@@ -1,311 +1,21 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
-import kotlinx.atomicfu.*
-import kotlinx.coroutines.internal.*
-import kotlin.coroutines.*
-
-private val DISPOSED_TASK = Symbol("REMOVED_TASK")
-
-// results for scheduleImpl
-private const val SCHEDULE_OK = 0
-private const val SCHEDULE_COMPLETED = 1
-private const val SCHEDULE_DISPOSED = 2
-
-private const val MS_TO_NS = 1_000_000L
-private const val MAX_MS = Long.MAX_VALUE / MS_TO_NS
-
-internal fun delayToNanos(timeMillis: Long): Long = when {
- timeMillis <= 0 -> 0L
- timeMillis >= MAX_MS -> Long.MAX_VALUE
- else -> timeMillis * MS_TO_NS
-}
-
-internal fun delayNanosToMillis(timeNanos: Long): Long =
- timeNanos / MS_TO_NS
-
-@Suppress("PrivatePropertyName")
-private val CLOSED_EMPTY = Symbol("CLOSED_EMPTY")
-
-private typealias Queue<T> = LockFreeTaskQueueCore<T>
-
-internal abstract class EventLoopImplBase: EventLoop(), Delay {
- // null | CLOSED_EMPTY | task | Queue<Runnable>
- private val _queue = atomic<Any?>(null)
-
- // Allocated only only once
- private val _delayed = atomic<ThreadSafeHeap<DelayedTask>?>(null)
-
+internal actual abstract class EventLoopImplPlatform: EventLoop() {
protected abstract val thread: Thread
- @Volatile
- private var isCompleted = false
-
- override val isEmpty: Boolean get() {
- if (!isUnconfinedQueueEmpty) return false
- val delayed = _delayed.value
- if (delayed != null && !delayed.isEmpty) return false
- val queue = _queue.value
- return when (queue) {
- null -> true
- is Queue<*> -> queue.isEmpty
- else -> queue === CLOSED_EMPTY
- }
- }
-
- protected override val nextTime: Long
- get() {
- if (super.nextTime == 0L) return 0L
- val queue = _queue.value
- when {
- queue === null -> {} // empty queue -- proceed
- queue is Queue<*> -> if (!queue.isEmpty) return 0 // non-empty queue
- queue === CLOSED_EMPTY -> return Long.MAX_VALUE // no more events -- closed
- else -> return 0 // non-empty queue
- }
- val delayed = _delayed.value ?: return Long.MAX_VALUE
- val nextDelayedTask = delayed.peek() ?: return Long.MAX_VALUE
- return (nextDelayedTask.nanoTime - timeSource.nanoTime()).coerceAtLeast(0)
- }
-
- private fun unpark() {
- val thread = thread
+ protected actual fun unpark() {
+ val thread = thread // atomic read
if (Thread.currentThread() !== thread)
- timeSource.unpark(thread)
+ unpark(thread)
}
- override fun shutdown() {
- // Clean up thread-local reference here -- this event loop is shutting down
- ThreadLocalEventLoop.resetEventLoop()
- // We should signal that this event loop should not accept any more tasks
- // and process queued events (that could have been added after last processNextEvent)
- isCompleted = true
- closeQueue()
- // complete processing of all queued tasks
- while (processNextEvent() <= 0) { /* spin */ }
- // reschedule the rest of delayed tasks
- rescheduleAllDelayed()
- }
-
- override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) =
- schedule(DelayedResumeTask(timeMillis, continuation))
-
- override fun processNextEvent(): Long {
- // unconfined events take priority
- if (processUnconfinedEvent()) return nextTime
- // queue all delayed tasks that are due to be executed
- val delayed = _delayed.value
- if (delayed != null && !delayed.isEmpty) {
- val now = timeSource.nanoTime()
- while (true) {
- // make sure that moving from delayed to queue removes from delayed only after it is added to queue
- // to make sure that 'isEmpty' and `nextTime` that check both of them
- // do not transiently report that both delayed and queue are empty during move
- delayed.removeFirstIf {
- if (it.timeToExecute(now)) {
- enqueueImpl(it)
- } else
- false
- } ?: break // quit loop when nothing more to remove or enqueueImpl returns false on "isComplete"
- }
- }
- // then process one event from queue
- dequeue()?.run()
- return nextTime
- }
-
- public final override fun dispatch(context: CoroutineContext, block: Runnable) = enqueue(block)
-
- public fun enqueue(task: Runnable) {
- if (enqueueImpl(task)) {
- // todo: we should unpark only when this delayed task became first in the queue
- unpark()
- } else {
- DefaultExecutor.enqueue(task)
- }
- }
-
- @Suppress("UNCHECKED_CAST")
- private fun enqueueImpl(task: Runnable): Boolean {
- _queue.loop { queue ->
- if (isCompleted) return false // fail fast if already completed, may still add, but queues will close
- when (queue) {
- null -> if (_queue.compareAndSet(null, task)) return true
- is Queue<*> -> {
- when ((queue as Queue<Runnable>).addLast(task)) {
- Queue.ADD_SUCCESS -> return true
- Queue.ADD_CLOSED -> return false
- Queue.ADD_FROZEN -> _queue.compareAndSet(queue, queue.next())
- }
- }
- else -> when {
- queue === CLOSED_EMPTY -> return false
- else -> {
- // update to full-blown queue to add one more
- val newQueue = Queue<Runnable>(Queue.INITIAL_CAPACITY, singleConsumer = true)
- newQueue.addLast(queue as Runnable)
- newQueue.addLast(task)
- if (_queue.compareAndSet(queue, newQueue)) return true
- }
- }
- }
- }
- }
-
- @Suppress("UNCHECKED_CAST")
- private fun dequeue(): Runnable? {
- _queue.loop { queue ->
- when (queue) {
- null -> return null
- is Queue<*> -> {
- val result = (queue as Queue<Runnable>).removeFirstOrNull()
- if (result !== Queue.REMOVE_FROZEN) return result as Runnable?
- _queue.compareAndSet(queue, queue.next())
- }
- else -> when {
- queue === CLOSED_EMPTY -> return null
- else -> if (_queue.compareAndSet(queue, null)) return queue as Runnable
- }
- }
- }
- }
-
- private fun closeQueue() {
- assert(isCompleted)
- _queue.loop { queue ->
- when (queue) {
- null -> if (_queue.compareAndSet(null, CLOSED_EMPTY)) return
- is Queue<*> -> {
- queue.close()
- return
- }
- else -> when {
- queue === CLOSED_EMPTY -> return
- else -> {
- // update to full-blown queue to close
- val newQueue = Queue<Runnable>(Queue.INITIAL_CAPACITY, singleConsumer = true)
- newQueue.addLast(queue as Runnable)
- if (_queue.compareAndSet(queue, newQueue)) return
- }
- }
- }
- }
-
- }
-
- internal fun schedule(delayedTask: DelayedTask) {
- when (scheduleImpl(delayedTask)) {
- SCHEDULE_OK -> if (shouldUnpark(delayedTask)) unpark()
- SCHEDULE_COMPLETED -> DefaultExecutor.schedule(delayedTask)
- SCHEDULE_DISPOSED -> {} // do nothing -- task was already disposed
- else -> error("unexpected result")
- }
- }
-
- private fun shouldUnpark(task: DelayedTask): Boolean = _delayed.value?.peek() === task
-
- private fun scheduleImpl(delayedTask: DelayedTask): Int {
- if (isCompleted) return SCHEDULE_COMPLETED
- val delayed = _delayed.value ?: run {
- _delayed.compareAndSet(null, ThreadSafeHeap())
- _delayed.value!!
- }
- return delayedTask.schedule(delayed, this)
- }
-
- // It performs "hard" shutdown for test cleanup purposes
- protected fun resetAll() {
- _queue.value = null
- _delayed.value = null
- }
-
- // This is a "soft" (normal) shutdown
- private fun rescheduleAllDelayed() {
- while (true) {
- /*
- * `removeFirstOrNull` below is the only operation on DelayedTask & ThreadSafeHeap that is not
- * synchronized on DelayedTask itself. All other operation are synchronized both on
- * DelayedTask & ThreadSafeHeap instances (in this order). It is still safe, because `dispose`
- * first removes DelayedTask from the heap (under synchronization) then
- * assign "_heap = DISPOSED_TASK", so there cannot be ever a race to _heap reference update.
- */
- val delayedTask = _delayed.value?.removeFirstOrNull() ?: break
- delayedTask.rescheduleOnShutdown()
- }
- }
-
- internal abstract class DelayedTask(
- timeMillis: Long
- ) : Runnable, Comparable<DelayedTask>, DisposableHandle, ThreadSafeHeapNode {
- private var _heap: Any? = null // null | ThreadSafeHeap | DISPOSED_TASK
-
- override var heap: ThreadSafeHeap<*>?
- get() = _heap as? ThreadSafeHeap<*>
- set(value) {
- require(_heap !== DISPOSED_TASK) // this can never happen, it is always checked before adding/removing
- _heap = value
- }
-
- override var index: Int = -1
-
- @JvmField val nanoTime: Long = timeSource.nanoTime() + delayToNanos(timeMillis)
-
- override fun compareTo(other: DelayedTask): Int {
- val dTime = nanoTime - other.nanoTime
- return when {
- dTime > 0 -> 1
- dTime < 0 -> -1
- else -> 0
- }
- }
-
- fun timeToExecute(now: Long): Boolean = now - nanoTime >= 0L
-
- @Synchronized
- fun schedule(delayed: ThreadSafeHeap<DelayedTask>, eventLoop: EventLoopImplBase): Int {
- if (_heap === DISPOSED_TASK) return SCHEDULE_DISPOSED // don't add -- was already disposed
- return if (delayed.addLastIf(this) { !eventLoop.isCompleted }) SCHEDULE_OK else SCHEDULE_COMPLETED
- }
-
- // note: DefaultExecutor.schedule performs `schedule` (above) which does sync & checks for DISPOSED_TASK
- fun rescheduleOnShutdown() = DefaultExecutor.schedule(this)
-
- @Synchronized
- final override fun dispose() {
- val heap = _heap
- if (heap === DISPOSED_TASK) return // already disposed
- @Suppress("UNCHECKED_CAST")
- (heap as? ThreadSafeHeap<DelayedTask>)?.remove(this) // remove if it is in heap (first)
- _heap = DISPOSED_TASK // never add again to any heap
- }
-
- override fun toString(): String = "Delayed[nanos=$nanoTime]"
- }
-
- private inner class DelayedResumeTask(
- timeMillis: Long,
- private val cont: CancellableContinuation<Unit>
- ) : DelayedTask(timeMillis) {
- init {
- // Note that this operation isn't lock-free, but very short
- cont.disposeOnCancellation(this)
- }
-
- override fun run() {
- with(cont) { resumeUndispatched(Unit) }
- }
- }
-
- // Cannot be moved to DefaultExecutor due to BE bug
- internal class DelayedRunnableTask(
- time: Long,
- private val block: Runnable
- ) : DelayedTask(time) {
- override fun run() { block.run() }
- override fun toString(): String = super.toString() + block.toString()
+ protected actual fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask) {
+ assert { this !== DefaultExecutor } // otherwise default execution was shutdown with tasks in it (cannot be)
+ DefaultExecutor.schedule(now, delayedTask)
}
}
@@ -336,4 +46,4 @@
*/
@InternalCoroutinesApi
public fun processNextEventInCurrentThread(): Long =
- ThreadLocalEventLoop.currentOrNull()?.processNextEvent() ?: Long.MAX_VALUE
+ ThreadLocalEventLoop.currentOrNull()?.processNextEvent() ?: Long.MAX_VALUE
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/src/Executors.kt b/kotlinx-coroutines-core/jvm/src/Executors.kt
index 876b44a..c5ce537 100644
--- a/kotlinx-coroutines-core/jvm/src/Executors.kt
+++ b/kotlinx-coroutines-core/jvm/src/Executors.kt
@@ -61,9 +61,9 @@
override fun dispatch(context: CoroutineContext, block: Runnable) {
try {
- executor.execute(timeSource.wrapTask(block))
+ executor.execute(wrapTask(block))
} catch (e: RejectedExecutionException) {
- timeSource.unTrackTask()
+ unTrackTask()
DefaultExecutor.enqueue(block)
}
}
diff --git a/kotlinx-coroutines-core/jvm/src/TimeSource.kt b/kotlinx-coroutines-core/jvm/src/TimeSource.kt
index fec1a82..99a0ca4 100644
--- a/kotlinx-coroutines-core/jvm/src/TimeSource.kt
+++ b/kotlinx-coroutines-core/jvm/src/TimeSource.kt
@@ -1,10 +1,14 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+// Need InlineOnly for efficient bytecode on Android
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "NOTHING_TO_INLINE")
+
package kotlinx.coroutines
-import java.util.concurrent.locks.LockSupport
+import java.util.concurrent.locks.*
+import kotlin.internal.InlineOnly
internal interface TimeSource {
fun currentTimeMillis(): Long
@@ -18,22 +22,48 @@
fun unpark(thread: Thread)
}
-internal object DefaultTimeSource : TimeSource {
- override fun currentTimeMillis(): Long = System.currentTimeMillis()
- override fun nanoTime(): Long = System.nanoTime()
- override fun wrapTask(block: Runnable): Runnable = block
- override fun trackTask() {}
- override fun unTrackTask() {}
- override fun registerTimeLoopThread() {}
- override fun unregisterTimeLoopThread() {}
+// For tests only
+// @JvmField: Don't use JvmField here to enable R8 optimizations via "assumenosideeffects"
+internal var timeSource: TimeSource? = null
- override fun parkNanos(blocker: Any, nanos: Long) {
- LockSupport.parkNanos(blocker, nanos)
- }
+@InlineOnly
+internal inline fun currentTimeMillis(): Long =
+ timeSource?.currentTimeMillis() ?: System.currentTimeMillis()
- override fun unpark(thread: Thread) {
- LockSupport.unpark(thread)
- }
+@InlineOnly
+internal actual inline fun nanoTime(): Long =
+ timeSource?.nanoTime() ?: System.nanoTime()
+
+@InlineOnly
+internal inline fun wrapTask(block: Runnable): Runnable =
+ timeSource?.wrapTask(block) ?: block
+
+@InlineOnly
+internal inline fun trackTask() {
+ timeSource?.trackTask()
}
-internal var timeSource: TimeSource = DefaultTimeSource
+@InlineOnly
+internal inline fun unTrackTask() {
+ timeSource?.unTrackTask()
+}
+
+@InlineOnly
+internal inline fun registerTimeLoopThread() {
+ timeSource?.registerTimeLoopThread()
+}
+
+@InlineOnly
+internal inline fun unregisterTimeLoopThread() {
+ timeSource?.unregisterTimeLoopThread()
+}
+
+@InlineOnly
+internal inline fun parkNanos(blocker: Any, nanos: Long) {
+ timeSource?.parkNanos(blocker, nanos) ?: LockSupport.parkNanos(blocker, nanos)
+}
+
+@InlineOnly
+internal inline fun unpark(thread: Thread) {
+ timeSource?.unpark(thread) ?: LockSupport.unpark(thread)
+}
diff --git a/kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt b/kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt
index 9d49ead..4bbf77d 100644
--- a/kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt
+++ b/kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -80,13 +80,13 @@
initialDelayMillis: Long,
channel: SendChannel<Unit>
) {
- var deadline = timeSource.nanoTime() + delayToNanos(initialDelayMillis)
+ var deadline = nanoTime() + delayToNanos(initialDelayMillis)
delay(initialDelayMillis)
val delayNs = delayToNanos(delayMillis)
while (true) {
deadline += delayNs
channel.send(Unit)
- val now = timeSource.nanoTime()
+ val now = nanoTime()
val nextDelay = (deadline - now).coerceAtLeast(0)
if (nextDelay == 0L && delayNs != 0L) {
val adjustedDelay = delayNs - (now - deadline) % delayNs
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ArrayCopy.kt b/kotlinx-coroutines-core/jvm/src/internal/ArrayCopy.kt
deleted file mode 100644
index f9196f3..0000000
--- a/kotlinx-coroutines-core/jvm/src/internal/ArrayCopy.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.internal
-
-internal actual fun <E> arraycopy(
- source: Array<E>,
- srcPos: Int,
- destination: Array<E?>,
- destinationStart: Int,
- length: Int
-) {
- System.arraycopy(source, srcPos, destination, destinationStart, length)
-}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
index 7d765b9..7d28de2 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
@@ -315,7 +315,7 @@
) : AbstractAtomicDesc() {
init {
// require freshly allocated node here
- check(node._next.value === node && node._prev.value === node)
+ assert { node._next.value === node && node._prev.value === node }
}
final override fun takeAffectedNode(op: OpDescriptor): Node {
@@ -390,7 +390,7 @@
@Suppress("UNCHECKED_CAST")
final override fun onPrepare(affected: Node, next: Node): Any? {
- check(affected !is LockFreeLinkedListHead)
+ assert { affected !is LockFreeLinkedListHead }
if (!validatePrepared(affected as T)) return REMOVE_PREPARED
// Note: onPrepare must use CAS to make sure the stale invocation is not
// going to overwrite the previous decision on successful preparation.
@@ -475,8 +475,8 @@
final override fun complete(op: AtomicOp<*>, failure: Any?) {
val success = failure == null
- val affectedNode = affectedNode ?: run { check(!success); return }
- val originalNext = originalNext ?: run { check(!success); return }
+ val affectedNode = affectedNode ?: run { assert { !success }; return }
+ val originalNext = originalNext ?: run { assert { !success }; return }
val update = if (success) updatedNext(affectedNode, originalNext) else originalNext
if (affectedNode._next.compareAndSet(op, update)) {
if (success) finishOnSuccess(affectedNode, originalNext)
@@ -564,7 +564,7 @@
while (true) {
if (cur is LockFreeLinkedListHead) return cur
cur = cur.nextNode
- check(cur !== this) { "Cannot loop to this while looking for list head" }
+ assert { cur !== this } // "Cannot loop to this while looking for list head"
}
}
@@ -648,8 +648,8 @@
}
internal fun validateNode(prev: Node, next: Node) {
- check(prev === this._prev.value)
- check(next === this._next.value)
+ assert { prev === this._prev.value }
+ assert { next === this._next.value }
}
override fun toString(): String = "${this::class.java.simpleName}@${Integer.toHexString(System.identityHashCode(this))}"
diff --git a/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt b/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt
index 5407ade..51dcee4 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt
@@ -4,9 +4,17 @@
package kotlinx.coroutines.internal
-@Suppress("ACTUAL_WITHOUT_EXPECT") // visibility
-internal actual typealias SynchronizedObject = Any
+import kotlinx.coroutines.*
-@PublishedApi
-internal actual inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T =
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual typealias SynchronizedObject = Any
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T =
kotlin.synchronized(lock, block)
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ThreadSafeHeap.kt b/kotlinx-coroutines-core/jvm/src/internal/ThreadSafeHeap.kt
index b164084..661a6bc 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/ThreadSafeHeap.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/ThreadSafeHeap.kt
@@ -1,159 +1,8 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
-import kotlinx.coroutines.*
-import java.util.*
-
-/**
- * @suppress **This an internal API and should not be used from general code.**
- */
-@InternalCoroutinesApi
-public interface ThreadSafeHeapNode {
- public var heap: ThreadSafeHeap<*>?
- public var index: Int
-}
-
-/**
- * Synchronized binary heap.
- * @suppress **This an internal API and should not be used from general code.**
- */
-@InternalCoroutinesApi
-public class ThreadSafeHeap<T> : SynchronizedObject() where T: ThreadSafeHeapNode, T: Comparable<T> {
- private var a: Array<T?>? = null
-
- @JvmField @Volatile
- public var size = 0
-
- public val isEmpty: Boolean get() = size == 0
-
- @Synchronized
- public fun clear() {
- Arrays.fill(a, 0, size, null)
- size = 0
- }
-
- @Synchronized
- public fun peek(): T? = firstImpl()
-
- @Synchronized
- public fun removeFirstOrNull(): T? =
- if (size > 0) {
- removeAtImpl(0)
- } else {
- null
- }
-
- // @Synchronized // NOTE! NOTE! NOTE! inline fun cannot be @Synchronized
- public inline fun removeFirstIf(predicate: (T) -> Boolean): T? = synchronized(this) {
- val first = firstImpl() ?: return null
- if (predicate(first)) {
- removeAtImpl(0)
- } else {
- null
- }
- }
-
- @Synchronized
- public fun addLast(node: T) = addImpl(node)
-
- // @Synchronized // NOTE! NOTE! NOTE! inline fun cannot be @Synchronized
- public inline fun addLastIf(node: T, cond: () -> Boolean): Boolean = synchronized(this) {
- if (cond()) {
- addImpl(node)
- true
- } else {
- false
- }
- }
-
- @Synchronized
- public fun remove(node: T): Boolean {
- return if (node.heap == null) {
- false
- } else {
- val index = node.index
- check(index >= 0)
- removeAtImpl(index)
- true
- }
- }
-
- @PublishedApi
- internal fun firstImpl(): T? = a?.get(0)
-
- @PublishedApi
- internal fun removeAtImpl(index: Int): T {
- check(size > 0)
- val a = this.a!!
- size--
- if (index < size) {
- swap(index, size)
- val j = (index - 1) / 2
- if (index > 0 && a[index]!! < a[j]!!) {
- swap(index, j)
- siftUpFrom(j)
- } else {
- siftDownFrom(index)
- }
- }
- val result = a[size]!!
- check(result.heap === this)
- result.heap = null
- result.index = -1
- a[size] = null
- return result
- }
-
- @PublishedApi
- internal fun addImpl(node: T) {
- check(node.heap == null)
- node.heap = this
- val a = realloc()
- val i = size++
- a[i] = node
- node.index = i
- siftUpFrom(i)
- }
-
- private tailrec fun siftUpFrom(i: Int) {
- if (i <= 0) return
- val a = a!!
- val j = (i - 1) / 2
- if (a[j]!! <= a[i]!!) return
- swap(i, j)
- siftUpFrom(j)
- }
-
- private tailrec fun siftDownFrom(i: Int) {
- var j = 2 * i + 1
- if (j >= size) return
- val a = a!!
- if (j + 1 < size && a[j + 1]!! < a[j]!!) j++
- if (a[i]!! <= a[j]!!) return
- swap(i, j)
- siftDownFrom(j)
- }
-
- @Suppress("UNCHECKED_CAST")
- private fun realloc(): Array<T?> {
- val a = this.a
- return when {
- a == null -> (arrayOfNulls<ThreadSafeHeapNode>(4) as Array<T?>).also { this.a = it }
- size >= a.size -> a.copyOf(size * 2).also { this.a = it }
- else -> a
- }
- }
-
- private fun swap(i: Int, j: Int) {
- val a = a!!
- val ni = a[j]!!
- val nj = a[i]!!
- a[i] = ni
- a[j] = nj
- ni.index = i
- nj.index = j
- }
-}
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun <T> clear(a: Array<T?>) = a.fill(null)
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
index e488905..4089710 100644
--- a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.scheduling
@@ -7,7 +7,7 @@
import kotlinx.atomicfu.*
import kotlinx.coroutines.*
import kotlinx.coroutines.internal.*
-import java.io.Closeable
+import java.io.*
import java.util.*
import java.util.concurrent.*
import java.util.concurrent.locks.*
@@ -146,7 +146,7 @@
val index = (top and PARKED_INDEX_MASK).toInt()
val updVersion = (top + PARKED_VERSION_INC) and PARKED_VERSION_MASK
val updIndex = worker.indexInArray
- assert(updIndex != 0) // only this worker can push itself, cannot be terminated
+ assert { updIndex != 0 } // only this worker can push itself, cannot be terminated
worker.nextParkedWorker = workers[index]
/*
* Other thread can be changing this worker's index at this point, but it
@@ -311,7 +311,7 @@
worker.join(timeout)
}
val state = worker.state
- check(state === WorkerState.TERMINATED) { "Expected TERMINATED state, but found $state"}
+ assert { state === WorkerState.TERMINATED } // Expected TERMINATED state
worker.localQueue.offloadAllWork(globalQueue)
}
}
@@ -325,7 +325,7 @@
// Shutdown current thread
currentWorker?.tryReleaseCpu(WorkerState.TERMINATED)
// check & cleanup state
- assert(cpuPermits.availablePermits() == corePoolSize)
+ assert { cpuPermits.availablePermits() == corePoolSize }
parkedWorkersStack.value = 0L
controlState.value = 0L
}
@@ -339,7 +339,7 @@
* @param fair whether the task should be dispatched fairly (strict FIFO) or not (semi-FIFO)
*/
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, fair: Boolean = false) {
- timeSource.trackTask() // this is needed for virtual time support
+ trackTask() // this is needed for virtual time support
val task = createTask(block, taskContext)
// try to submit the task to the local queue and act depending on the result
when (submitToLocalQueue(task, fair)) {
@@ -596,7 +596,7 @@
val thread = Thread.currentThread()
thread.uncaughtExceptionHandler.uncaughtException(thread, e)
} finally {
- timeSource.unTrackTask()
+ unTrackTask()
}
}
@@ -664,9 +664,8 @@
* This attempt may fail either because worker terminated itself or because someone else
* claimed this worker (though this case is rare, because require very bad timings)
*/
- fun tryForbidTermination(): Boolean {
- val state = terminationState.value
- return when (state) {
+ fun tryForbidTermination(): Boolean =
+ when (val state = terminationState.value) {
TERMINATED -> false // already terminated
FORBIDDEN -> false // already forbidden, someone else claimed this worker
ALLOWED -> terminationState.compareAndSet(
@@ -675,7 +674,6 @@
)
else -> error("Invalid terminationState = $state")
}
- }
/**
* Tries to acquire CPU token if worker doesn't have one
@@ -780,7 +778,7 @@
val currentState = state
// Shutdown sequence of blocking dispatcher
if (currentState !== WorkerState.TERMINATED) {
- assert(currentState == WorkerState.BLOCKING) { "Expected BLOCKING state, but has $currentState" }
+ assert { currentState == WorkerState.BLOCKING } // "Expected BLOCKING state, but has $currentState"
state = WorkerState.RETIRING
}
}
@@ -927,7 +925,7 @@
terminationDeadline = 0L // reset deadline for termination
lastStealIndex = 0 // reset steal index (next time try random)
if (state == WorkerState.PARKING) {
- assert(mode == TaskMode.PROBABLY_BLOCKING)
+ assert { mode == TaskMode.PROBABLY_BLOCKING }
state = WorkerState.BLOCKING
parkTimeNs = MIN_PARK_TIME_NS
}
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt b/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt
index a0f209b..a9aa86d 100644
--- a/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt
@@ -5,6 +5,7 @@
package kotlinx.coroutines.scheduling
import kotlinx.atomicfu.*
+import kotlinx.coroutines.*
import java.util.concurrent.atomic.*
internal const val BUFFER_CAPACITY_BASE = 7
diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt
index 0a10913..073c7a5 100644
--- a/kotlinx-coroutines-core/jvm/test/TestBase.kt
+++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt
@@ -9,6 +9,7 @@
import org.junit.*
import java.util.*
import java.util.concurrent.atomic.*
+import kotlin.coroutines.*
import kotlin.test.*
private val VERBOSE = systemProp("test.verbose", false)
@@ -213,4 +214,6 @@
assertTrue(result.exceptionOrNull() is T, "Expected ${T::class}, but had $result")
return result.exceptionOrNull()!! as T
}
+
+ protected suspend fun currentDispatcher() = coroutineContext[ContinuationInterceptor]!!
}
diff --git a/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt
index 009ae00..ca399f5 100644
--- a/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt
+++ b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
@@ -20,7 +20,7 @@
} finally {
DefaultExecutor.shutdown(SHUTDOWN_TIMEOUT)
testTimeSource.shutdown()
- timeSource = DefaultTimeSource // restore time source
+ timeSource = null // restore time source
}
}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt b/kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt
index cffe6c0..5178907 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt
@@ -112,18 +112,15 @@
var sum = 0
var n = 0
whileSelect {
- this@averageInTimeWindow.onReceiveOrNull {
- when (it) {
- null -> {
- // Send leftovers and bail out
- if (n != 0) send(sum / n.toDouble())
- false
- }
- else -> {
- sum += it
- ++n
- true
- }
+ this@averageInTimeWindow.onReceiveOrClosed {
+ if (it.isClosed) {
+ // Send leftovers and bail out
+ if (n != 0) send(sum / n.toDouble())
+ false
+ } else {
+ sum += it.value
+ ++n
+ true
}
}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt
index 3b5e173..db5fabc 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt
@@ -6,9 +6,11 @@
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.intrinsics.*
import kotlinx.coroutines.selects.*
import org.junit.Test
import java.util.concurrent.*
+import kotlin.coroutines.*
import kotlin.test.*
/*
@@ -271,4 +273,40 @@
checkCycles(e)
}
}
+
+
+ private suspend fun throws() {
+ yield() // TCE
+ throw RecoverableTestException()
+ }
+
+ private suspend fun awaiter() {
+ val task = GlobalScope.async(Dispatchers.Default, start = CoroutineStart.LAZY) { throws() }
+ task.await()
+ yield() // TCE
+ }
+
+ @Test
+ fun testNonDispatchedRecovery() {
+ val await = suspend { awaiter() }
+
+ val barrier = CyclicBarrier(2)
+ var exception: Throwable? = null
+ await.startCoroutineUnintercepted(Continuation(EmptyCoroutineContext) {
+ exception = it.exceptionOrNull()
+ barrier.await()
+ })
+
+ barrier.await()
+ val e = exception
+ assertNotNull(e)
+ verifyStackTrace(e, "kotlinx.coroutines.RecoverableTestException\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.throws(StackTraceRecoveryTest.kt:280)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$throws\$1.invokeSuspend(StackTraceRecoveryTest.kt)\n" +
+ "\t(Coroutine boundary)\n" +
+ "\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.awaiter(StackTraceRecoveryTest.kt:285)\n" +
+ "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testNonDispatchedRecovery\$await\$1.invokeSuspend(StackTraceRecoveryTest.kt:291)\n" +
+ "Caused by: kotlinx.coroutines.RecoverableTestException")
+ }
}
diff --git a/kotlinx-coroutines-core/jvm/test/flow/FlatMapStressTest.kt b/kotlinx-coroutines-core/jvm/test/flow/FlatMapStressTest.kt
index 9092a18..699d9c6 100644
--- a/kotlinx-coroutines-core/jvm/test/flow/FlatMapStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/flow/FlatMapStressTest.kt
@@ -5,6 +5,7 @@
package kotlinx.coroutines.flow
import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.internal.*
import kotlinx.coroutines.scheduling.*
import org.junit.Assume.*
import org.junit.Test
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt
index 89a9370..dcb36af 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt
index 6aa62d2..4f1277d 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-02b.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02b.kt
index 67d6f67..a78840d 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-02b.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02b.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt
index b7c36f1..e6a299e 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-03s.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03s.kt
index 253d691..13cf679 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-03s.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03s.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt
index 98abe6b..3afa0fe 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt
index 7a4340e..e6a9112 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt
index ff6db92..60de941 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt
index 58eadd6..a348ef4 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt
index ab699f2..e44b703 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt
index cbb3d62..518c0be 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
@@ -9,13 +9,13 @@
fun main() = runBlocking {
//sampleStart
- val startTime = timeSource.currentTimeMillis()
+ val startTime = currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5) { // computation loop, just wastes CPU
// print a message twice a second
- if (timeSource.currentTimeMillis() >= nextPrintTime) {
+ if (currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt
index bebb94b..8c1e3f8 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
@@ -9,13 +9,13 @@
fun main() = runBlocking {
//sampleStart
- val startTime = timeSource.currentTimeMillis()
+ val startTime = currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (isActive) { // cancellable computation loop
// print a message twice a second
- if (timeSource.currentTimeMillis() >= nextPrintTime) {
+ if (currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt
index fcad730..002521e 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt
index 08bb1e2..5c7debb 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt
index da8ecbd..299ceb2 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt
index 3fbbd09..1116f91 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt
index ab437ed..d3ab53b 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt
index d65f0f2..9ab469f 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt
index 84419aa..c6550b4 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt
index 87fd355..02ac7bb 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt
index 5f9d247..625b52c 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt
index 7224ffa..b88a1b0 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt
index 64df2ec..0487296 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt
index f1e9d9b..6c55980 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt
index ee7afab..ae9d95c 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt
index 4615e30..43ceea5 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt
index 6c027e4..5dfb770 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt
index 1cb75d9..d78e514 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt
index 4a919fb..aa6dd6f 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt
index d840f70..ea6860e 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt
index 81e75a8..e3c5403 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt
index 6346842..1df506e 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt
index 1471f72..13534c7 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt
index 7a29aed..d7be586 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt
index 5740495..a26d3a0 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt
index 6624252..55cfecc 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt
index f39ffd9..d014f56 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt
index ba0b04c..563d418 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt
index 992e672..27bff49 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt
index bcc754a..2a278d2 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt
index 58b1dd0..b72041b 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt
index 5a7b3d5..bce5f14 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt
index 1945495..a26d8c6 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt
index c931409..4bec14f 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt
index 46f6dab..80da1df 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt
index 1166519..eadbb9b 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt
index a55be12..e741d39 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt
index 5271873..e90606f 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt
index 076a097..0faf8d4 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt
index f49cab6..24beb56 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt
index 02bd98c..c763f81 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt
index 7b79bbe..1837aae 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt
index 3da88f1..1c7818d 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt
index fbbad49..6cda382 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt
index eac450a..d70b6c9 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt
index 47a3525..facc2e0 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt
index c50b263..47c31b9 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt
index 66c7be3..e0aeeb3 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt
index d624c18..84376f7 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt
index ebb7144..a2e503f 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt
index c55ba44..cdf1f3c 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt
index 8bec253..72dd1f7 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt
index 5cf09eb..7a401e9 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt
index 9990200..0ef8a2a 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/TestUtil.kt b/kotlinx-coroutines-core/jvm/test/guide/test/TestUtil.kt
index 786338c..83150e1 100644
--- a/kotlinx-coroutines-core/jvm/test/guide/test/TestUtil.kt
+++ b/kotlinx-coroutines-core/jvm/test/guide/test/TestUtil.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.guide.test
@@ -11,7 +11,7 @@
import java.io.*
import java.util.concurrent.*
-fun wrapTask(block: Runnable) = timeSource.wrapTask(block)
+fun wrapTask(block: Runnable) = kotlinx.coroutines.wrapTask(block)
// helper function to dump exception to stdout for ease of debugging failed tests
private inline fun <T> outputException(name: String, block: () -> T): T =
diff --git a/kotlinx-coroutines-core/native/src/CoroutineContext.kt b/kotlinx-coroutines-core/native/src/CoroutineContext.kt
index 017a01c..41d2c88 100644
--- a/kotlinx-coroutines-core/native/src/CoroutineContext.kt
+++ b/kotlinx-coroutines-core/native/src/CoroutineContext.kt
@@ -5,33 +5,24 @@
package kotlinx.coroutines
import kotlin.coroutines.*
-import kotlinx.coroutines.internal.*
private fun takeEventLoop(): EventLoopImpl =
ThreadLocalEventLoop.currentOrNull() as? EventLoopImpl ?:
error("There is no event loop. Use runBlocking { ... } to start one.")
-internal object DefaultExecutor : CoroutineDispatcher(), Delay {
+internal actual object DefaultExecutor : CoroutineDispatcher(), Delay {
override fun dispatch(context: CoroutineContext, block: Runnable) =
takeEventLoop().dispatch(context, block)
- override fun scheduleResumeAfterDelay(time: Long, continuation: CancellableContinuation<Unit>) =
- takeEventLoop().scheduleResumeAfterDelay(time, continuation)
- override fun invokeOnTimeout(time: Long, block: Runnable): DisposableHandle =
- takeEventLoop().invokeOnTimeout(time, block)
+ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) =
+ takeEventLoop().scheduleResumeAfterDelay(timeMillis, continuation)
+ override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle =
+ takeEventLoop().invokeOnTimeout(timeMillis, block)
- fun enqueue(task: Runnable): Boolean {
- error("Cannot execute task because event loop was shut down")
- }
-
- fun schedule(delayedTask: EventLoopImpl.DelayedTask) {
- error("Cannot schedule task because event loop was shut down")
- }
-
- fun removeDelayedImpl(delayedTask: EventLoopImpl.DelayedTask) {
- error("Cannot happen")
- }
+ actual fun enqueue(task: Runnable): Unit = loopWasShutDown()
}
+internal fun loopWasShutDown(): Nothing = error("Cannot execute task because event loop was shut down")
+
internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
DefaultExecutor
@@ -39,8 +30,8 @@
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = coroutineContext + context
- return if (combined !== kotlinx.coroutines.DefaultExecutor && combined[ContinuationInterceptor] == null)
- combined + kotlinx.coroutines.DefaultExecutor else combined
+ return if (combined !== DefaultExecutor && combined[ContinuationInterceptor] == null)
+ combined + DefaultExecutor else combined
}
// No debugging facilities on native
diff --git a/kotlinx-coroutines-core/native/src/Debug.kt b/kotlinx-coroutines-core/native/src/Debug.kt
index e81e89a..653cb06 100644
--- a/kotlinx-coroutines-core/native/src/Debug.kt
+++ b/kotlinx-coroutines-core/native/src/Debug.kt
@@ -1,15 +1,18 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines
import kotlin.math.*
+internal actual val DEBUG: Boolean = false
+
internal actual val Any.hexAddress: String get() = abs(id().let { if (it == Int.MIN_VALUE) 0 else it }).toString(16)
internal actual val Any.classSimpleName: String get() = this::class.simpleName ?: "Unknown"
-
@SymbolName("Kotlin_Any_hashCode")
external fun Any.id(): Int // Note: can return negative value on K/N
+
+internal actual inline fun assert(value: () -> Boolean) {}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/native/src/EventLoop.kt b/kotlinx-coroutines-core/native/src/EventLoop.kt
index 37f5442..8f2dfeb 100644
--- a/kotlinx-coroutines-core/native/src/EventLoop.kt
+++ b/kotlinx-coroutines-core/native/src/EventLoop.kt
@@ -4,286 +4,19 @@
package kotlinx.coroutines
-import kotlinx.atomicfu.*
-import kotlinx.cinterop.*
-import kotlinx.coroutines.internal.*
-import platform.posix.*
-import kotlin.coroutines.*
import kotlin.system.*
-private const val DELAYED = 0
-private const val REMOVED = 1
-private const val RESCHEDULED = 2
-
-private const val MS_TO_NS = 1_000_000L
-private const val MAX_MS = Long.MAX_VALUE / MS_TO_NS
-
-private fun delayToNanos(timeMillis: Long): Long = when {
- timeMillis <= 0 -> 0L
- timeMillis >= MAX_MS -> Long.MAX_VALUE
- else -> timeMillis * MS_TO_NS
+internal actual abstract class EventLoopImplPlatform: EventLoop() {
+ protected actual fun unpark() { /* does nothing */ }
+ protected actual fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask): Unit =
+ loopWasShutDown()
}
-@Suppress("PrivatePropertyName")
-@SharedImmutable
-private val CLOSED_EMPTY = Symbol("CLOSED_EMPTY")
-
-private typealias Queue<T> = LockFreeMPSCQueueCore<T>
-
-internal class EventLoopImpl: EventLoop(), Delay {
- // null | CLOSED_EMPTY | task | Queue<Runnable>
- private val _queue = atomic<Any?>(null)
-
- // Allocated only once
- private val _delayed = atomic<ThreadSafeHeap<DelayedTask>?>(null)
-
- private var isCompleted = false
-
- override val isEmpty: Boolean get() {
- if (!isUnconfinedQueueEmpty) return false
- val delayed = _delayed.value
- if (delayed != null && !delayed.isEmpty) return false
- val queue = _queue.value
- return when (queue) {
- null -> true
- is Queue<*> -> queue.isEmpty
- else -> queue === CLOSED_EMPTY
- }
- }
-
- protected override val nextTime: Long
- get() {
- if (super.nextTime == 0L) return 0L
- val queue = _queue.value
- when {
- queue === null -> {} // empty queue -- proceed
- queue is Queue<*> -> if (!queue.isEmpty) return 0 // non-empty queue
- queue === CLOSED_EMPTY -> return Long.MAX_VALUE // no more events -- closed
- else -> return 0 // non-empty queue
- }
- val delayed = _delayed.value ?: return Long.MAX_VALUE
- val nextDelayedTask = delayed.peek() ?: return Long.MAX_VALUE
- return (nextDelayedTask.nanoTime - nanoTime()).coerceAtLeast(0)
- }
-
- override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) =
- schedule(DelayedResumeTask(timeMillis, continuation))
-
+internal class EventLoopImpl: EventLoopImplBase() {
override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle =
- DelayedRunnableTask(timeMillis, block).also { schedule(it) }
-
- override fun processNextEvent(): Long {
- // unconfined events take priority
- if (processUnconfinedEvent()) return nextTime
- // queue all delayed tasks that are due to be executed
- val delayed = _delayed.value
- if (delayed != null && !delayed.isEmpty) {
- val now = nanoTime()
- while (true) {
- // make sure that moving from delayed to queue removes from delayed only after it is added to queue
- // to make sure that 'isEmpty' and `nextTime` that check both of them
- // do not transiently report that both delayed and queue are empty during move
- delayed.removeFirstIf {
- if (it.timeToExecute(now)) {
- enqueueImpl(it)
- } else
- false
- } ?: break // quit loop when nothing more to remove or enqueueImpl returns false on "isComplete"
- }
- }
- // then process one event from queue
- dequeue()?.run()
- return nextTime
- }
-
- public final override fun dispatch(context: CoroutineContext, block: Runnable) = enqueue(block)
-
- public fun enqueue(task: Runnable) {
- if (enqueueImpl(task)) {
- // todo: we should unpark only when this delayed task became first in the queue
- unpark()
- } else {
- DefaultExecutor.enqueue(task)
- }
- }
-
- @Suppress("UNCHECKED_CAST")
- private fun enqueueImpl(task: Runnable): Boolean {
- _queue.loop { queue ->
- if (isCompleted) return false // fail fast if already completed, may still add, but queues will close
- when (queue) {
- null -> if (_queue.compareAndSet(null, task)) return true
- is Queue<*> -> {
- when ((queue as Queue<Runnable>).addLast(task)) {
- Queue.ADD_SUCCESS -> return true
- Queue.ADD_CLOSED -> return false
- Queue.ADD_FROZEN -> _queue.compareAndSet(queue, queue.next())
- }
- }
- else -> when {
- queue === CLOSED_EMPTY -> return false
- else -> {
- // update to full-blown queue to add one more
- val newQueue = Queue<Runnable>(Queue.INITIAL_CAPACITY)
- newQueue.addLast(queue as Runnable)
- newQueue.addLast(task)
- if (_queue.compareAndSet(queue, newQueue)) return true
- }
- }
- }
- }
- }
-
- @Suppress("UNCHECKED_CAST")
- private fun dequeue(): Runnable? {
- _queue.loop { queue ->
- when (queue) {
- null -> return null
- is Queue<*> -> {
- val result = (queue as Queue<Runnable>).removeFirstOrNull()
- if (result !== Queue.REMOVE_FROZEN) return result as Runnable?
- _queue.compareAndSet(queue, queue.next())
- }
- else -> when {
- queue === CLOSED_EMPTY -> return null
- else -> if (_queue.compareAndSet(queue, null)) return queue as Runnable
- }
- }
- }
- }
-
- protected fun closeQueue() {
- assert(isCompleted)
- _queue.loop { queue ->
- when (queue) {
- null -> if (_queue.compareAndSet(null, CLOSED_EMPTY)) return
- is Queue<*> -> {
- queue.close()
- return
- }
- else -> when {
- queue === CLOSED_EMPTY -> return
- else -> {
- // update to full-blown queue to close
- val newQueue = Queue<Runnable>(Queue.INITIAL_CAPACITY)
- newQueue.addLast(queue as Runnable)
- if (_queue.compareAndSet(queue, newQueue)) return
- }
- }
- }
- }
-
- }
-
- internal fun schedule(delayedTask: DelayedTask) {
- if (scheduleImpl(delayedTask)) {
- // todo: we should unpark only when this delayed task became first in the queue
- unpark()
- } else
- DefaultExecutor.schedule(delayedTask)
- }
-
- private fun scheduleImpl(delayedTask: DelayedTask): Boolean {
- if (isCompleted) return false
- val delayed = _delayed.value ?: run {
- _delayed.compareAndSet(null, ThreadSafeHeap())
- _delayed.value!!
- }
- return delayed.addLastIf(delayedTask) { !isCompleted }
- }
-
- internal fun removeDelayedImpl(delayedTask: DelayedTask) {
- _delayed.value?.remove(delayedTask)
- }
-
- // It performs "hard" shutdown for test cleanup purposes
- protected fun resetAll() {
- _queue.value = null
- _delayed.value = null
- }
-
- // This is a "soft" (normal) shutdown
- protected fun rescheduleAllDelayed() {
- while (true) {
- val delayedTask = _delayed.value?.removeFirstOrNull() ?: break
- delayedTask.rescheduleOnShutdown()
- }
- }
-
- override fun shutdown() {
- // Clean up thread-local reference here -- this event loop is shutting down
- ThreadLocalEventLoop.resetEventLoop()
- // We should signal that ThreadEventLoop should not accept any more tasks
- // and process queued events (that could have been added after last processNextEvent)
- isCompleted = true
- closeQueue()
- // complete processing of all queued tasks
- while (processNextEvent() <= 0) { /* spin */ }
- // reschedule the rest of delayed tasks
- rescheduleAllDelayed()
- }
-
- internal abstract inner class DelayedTask(
- timeMillis: Long
- ) : Runnable, Comparable<DelayedTask>, DisposableHandle, ThreadSafeHeapNode {
- override var index: Int = -1
- var state = DELAYED // Guarded by by lock on this task for reschedule/dispose purposes
- val nanoTime: Long = nanoTime() + delayToNanos(timeMillis)
-
- override fun compareTo(other: DelayedTask): Int {
- val dTime = nanoTime - other.nanoTime
- return when {
- dTime > 0 -> 1
- dTime < 0 -> -1
- else -> 0
- }
- }
-
- fun timeToExecute(now: Long): Boolean = now - nanoTime >= 0L
-
- fun rescheduleOnShutdown() {
- if (state != DELAYED) return
- if (_delayed.value!!.remove(this)) {
- state = RESCHEDULED
- DefaultExecutor.schedule(this)
- } else
- state = REMOVED
- }
-
- final override fun dispose() {
- when (state) {
- DELAYED -> _delayed.value?.remove(this)
- RESCHEDULED -> DefaultExecutor.removeDelayedImpl(this)
- else -> return
- }
- state = REMOVED
- }
-
- override fun toString(): String = "Delayed[nanos=$nanoTime]"
- }
-
- private inner class DelayedResumeTask(
- timeMillis: Long,
- private val cont: CancellableContinuation<Unit>
- ) : DelayedTask(timeMillis) {
- override fun run() {
- with(cont) { resumeUndispatched(Unit) }
- }
- }
-
- private inner class DelayedRunnableTask(
- timeMillis: Long,
- private val block: Runnable
- ) : DelayedTask(timeMillis) {
- override fun run() { block.run() }
- override fun toString(): String = super.toString() + block.toString()
- }
+ scheduleInvokeOnTimeout(timeMillis, block)
}
internal actual fun createEventLoop(): EventLoop = EventLoopImpl()
-private fun nanoTime(): Long {
- return getTimeNanos()
-}
-
-private fun unpark(): Unit { /* does nothing */ }
+internal actual fun nanoTime(): Long = getTimeNanos()
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/native/src/internal/ArrayCopy.kt b/kotlinx-coroutines-core/native/src/internal/ArrayCopy.kt
deleted file mode 100644
index f4f2831..0000000
--- a/kotlinx-coroutines-core/native/src/internal/ArrayCopy.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.internal
-
-internal actual fun <E> arraycopy(
- source: Array<E>,
- srcPos: Int,
- destination: Array<E?>,
- destinationStart: Int,
- length: Int
-) {
- var destinationIndex = destinationStart
- for (sourceIndex in srcPos until srcPos + length) {
- destination[destinationIndex++] = source[sourceIndex]
- }
-}
diff --git a/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt b/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt
index 252f938..c81f653 100644
--- a/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt
+++ b/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt
@@ -21,9 +21,18 @@
override fun add(index: Int, element: E) {
rangeCheck(index)
val update = arrayOfNulls<Any?>(if (array.size == _size) array.size * 2 else array.size)
- arraycopy(array, 0, update, 0, index)
+ array.copyInto(
+ destination = update,
+ endIndex = index
+ )
update[index] = element
- arraycopy(array, index, update, index + 1, _size - index + 1)
+ array.copyInto(
+ destination = update,
+ destinationOffset = index + 1,
+ startIndex = index,
+ endIndex = _size + 1
+ )
+ ++_size
array = update
}
@@ -43,8 +52,16 @@
val n = array.size
val element = array[index]
val update = arrayOfNulls<Any>(n)
- arraycopy(array, 0, update, 0, index)
- arraycopy(array, index + 1, update, index, n - index - 1)
+ array.copyInto(
+ destination = update,
+ endIndex = index
+ )
+ array.copyInto(
+ destination = update,
+ destinationOffset = index,
+ startIndex = index + 1,
+ endIndex = n
+ )
array = update
--_size
return element as E
diff --git a/kotlinx-coroutines-core/native/src/internal/LockFreeMPSCQueue.kt b/kotlinx-coroutines-core/native/src/internal/LockFreeMPSCQueue.kt
deleted file mode 100644
index ae2c017..0000000
--- a/kotlinx-coroutines-core/native/src/internal/LockFreeMPSCQueue.kt
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.internal
-
-import kotlinx.atomicfu.*
-
-private typealias Core<E> = LockFreeMPSCQueueCore<E>
-
-/**
- * Lock-free Multiply-Producer Single-Consumer Queue.
- * *Note: This queue is NOT linearizable. It provides only quiescent consistency for its operations.*
- *
- * In particular, the following execution is permitted for this queue, but is not permitted for a linearizable queue:
- *
- * ```
- * Thread 1: addLast(1) = true, removeFirstOrNull() = null
- * Thread 2: addLast(2) = 2 // this operation is concurrent with both operations in the first thread
- * ```
- *
- * @suppress **This is unstable API and it is subject to change.**
- */
-class LockFreeMPSCQueue<E : Any> {
- private val _cur = atomic(Core<E>(Core.INITIAL_CAPACITY))
-
- // Note: it is not atomic w.r.t. remove operation (remove can transiently fail when isEmpty is false)
- val isEmpty: Boolean get() = _cur.value.isEmpty
-
- fun close() {
- _cur.loop { cur ->
- if (cur.close()) return // closed this copy
- _cur.compareAndSet(cur, cur.next()) // move to next
- }
- }
-
- fun addLast(element: E): Boolean {
- _cur.loop { cur ->
- when (cur.addLast(element)) {
- Core.ADD_SUCCESS -> return true
- Core.ADD_CLOSED -> return false
- Core.ADD_FROZEN -> _cur.compareAndSet(cur, cur.next()) // move to next
- }
- }
- }
-
- @Suppress("UNCHECKED_CAST")
- fun removeFirstOrNull(): E? {
- _cur.loop { cur ->
- val result = cur.removeFirstOrNull()
- if (result !== Core.REMOVE_FROZEN) return result as E?
- _cur.compareAndSet(cur, cur.next())
- }
- }
-}
-
-/**
- * Lock-free Multiply-Producer Single-Consumer Queue core.
- * *Note: This queue is NOT linearizable. It provides only quiescent consistency for its operations.*
- *
- * @see LockFreeMPSCQueue
- * @suppress **This is unstable API and it is subject to change.**
- */
-internal class LockFreeMPSCQueueCore<E : Any>(private val capacity: Int) {
- private val mask = capacity - 1
- private val _next = atomic<Core<E>?>(null)
- private val _state = atomic(0L)
- private val array = arrayOfNulls<Any?>(capacity);
-
- init {
- check(mask <= MAX_CAPACITY_MASK)
- check(capacity and mask == 0)
- }
-
- // Note: it is not atomic w.r.t. remove operation (remove can transiently fail when isEmpty is false)
- val isEmpty: Boolean get() = _state.value.withState { head, tail -> head == tail }
-
- fun close(): Boolean {
- _state.update { state ->
- if (state and CLOSED_MASK != 0L) return true // ok - already closed
- if (state and FROZEN_MASK != 0L) return false // frozen -- try next
- state or CLOSED_MASK // try set closed bit
- }
- return true
- }
-
- // ADD_CLOSED | ADD_FROZEN | ADD_SUCCESS
- fun addLast(element: E): Int {
- _state.loop { state ->
- if (state and (FROZEN_MASK or CLOSED_MASK) != 0L) return state.addFailReason() // cannot add
- state.withState { head, tail ->
- // there could be one REMOVE element beyond head that we cannot stump up,
- // so we check for full queue with an extra margin of one element
- if ((tail + 2) and mask == head and mask) return ADD_FROZEN // overfull, so do freeze & copy
- val newTail = (tail + 1) and MAX_CAPACITY_MASK
- if (_state.compareAndSet(state, state.updateTail(newTail))) {
- // successfully added
- array[tail and mask] = element
- // could have been frozen & copied before this item was set -- correct it by filling placeholder
- var cur = this
- while(true) {
- if (cur._state.value and FROZEN_MASK == 0L) break // all fine -- not frozen yet
- cur = cur.next().fillPlaceholder(tail, element) ?: break
- }
- return ADD_SUCCESS // added successfully
- }
- }
- }
- }
-
- private fun fillPlaceholder(index: Int, element: E): Core<E>? {
-// if (array.compareAndSet(index and mask, PLACEHOLDER, element)) {
-// // we've corrected missing element, should check if that propagated to further copies, just in case
-// return this
-// }
-// // it is Ok, no need for further action
-// return null
- if (array[index and mask] != PLACEHOLDER) return null
- array[index and mask] = element
- return this
- }
-
- // SINGLE CONSUMER
- // REMOVE_FROZEN | null (EMPTY) | E (SUCCESS)
- fun removeFirstOrNull(): Any? {
- _state.loop { state ->
- if (state and FROZEN_MASK != 0L) return REMOVE_FROZEN // frozen -- cannot modify
- state.withState { head, tail ->
- if ((tail and mask) == (head and mask)) return null // empty
- // because queue is Single Consumer, then element == null|PLACEHOLDER can only be when add has not finished yet
- val element = array[head and mask] ?: return null
- if (element === PLACEHOLDER) return null // same story -- consider it not added yet
- check(element !== REMOVED) { "This queue can have only one consumer" }
- // tentatively remove element to let GC work
- // we cannot put null into array, because copying thread could replace it with PLACEHOLDER
- // and that is a disaster, so a separate REMOVED token is used here.
- // Note: at most one REMOVED in the array, because single consumer.
- array[head and mask] = REMOVED
- val newHead = (head + 1) and MAX_CAPACITY_MASK
- if (_state.compareAndSet(state, state.updateHead(newHead))) {
- array[head and mask] = null // now can safely put null (state was updated)
- return element // successfully removed in fast-path
- }
- // Slow-path for remove in case of interference
- var cur = this
- while (true) {
- cur = cur.removeSlowPath(head, newHead) ?: return element
- }
- }
- }
- }
-
- private fun removeSlowPath(oldHead: Int, newHead: Int): Core<E>? {
- _state.loop { state ->
- state.withState { head, _ ->
- check(head == oldHead) { "This queue can have only one consumer" }
- if (state and FROZEN_MASK != 0L) {
- // state was already frozen, so either old or REMOVED item could have been copied to next
- val next = next()
- next.array[head and next.mask] = REMOVED // make sure it is removed in new array regardless
- return next // continue to correct head in next
- }
- if (_state.compareAndSet(state, state.updateHead(newHead))) {
- array[head and mask] = null // now can safely put null (state was updated)
- return null
- }
- }
- }
- }
-
- fun next(): LockFreeMPSCQueueCore<E> = allocateOrGetNextCopy(markFrozen())
-
- private fun markFrozen(): Long =
- _state.updateAndGet { state ->
- if (state and FROZEN_MASK != 0L) return state // already marked
- state or FROZEN_MASK
- }
-
- private fun allocateOrGetNextCopy(state: Long): Core<E> {
- _next.loop { next ->
- if (next != null) return next // already allocated & copied
- _next.compareAndSet(null, allocateNextCopy(state))
- }
- }
-
- private fun allocateNextCopy(state: Long): Core<E> {
- val next = LockFreeMPSCQueueCore<E>(capacity * 2)
- state.withState { head, tail ->
- var index = head
- while (index and mask != tail and mask) {
- // replace nulls with placeholders on copy
- next.array[index and next.mask] = array[index and mask] ?: PLACEHOLDER
- index++
- }
- next._state.value = state wo FROZEN_MASK
- }
- return next
- }
-
- @Suppress("PrivatePropertyName")
- internal companion object {
- internal const val INITIAL_CAPACITY = 8
-
- private const val CAPACITY_BITS = 30
- private const val MAX_CAPACITY_MASK = (1 shl CAPACITY_BITS) - 1
- private const val HEAD_SHIFT = 0
- private const val HEAD_MASK = MAX_CAPACITY_MASK.toLong() shl HEAD_SHIFT
- private const val TAIL_SHIFT = HEAD_SHIFT + CAPACITY_BITS
- private const val TAIL_MASK = MAX_CAPACITY_MASK.toLong() shl TAIL_SHIFT
-
- private const val FROZEN_SHIFT = TAIL_SHIFT + CAPACITY_BITS
- private const val FROZEN_MASK = 1L shl FROZEN_SHIFT
- private const val CLOSED_SHIFT = FROZEN_SHIFT + 1
- private const val CLOSED_MASK = 1L shl CLOSED_SHIFT
- @SharedImmutable
- internal val REMOVE_FROZEN = Symbol("REMOVE_FROZEN")
-
- internal const val ADD_SUCCESS = 0
- internal const val ADD_FROZEN = 1
- internal const val ADD_CLOSED = 2
- @SharedImmutable
- private val PLACEHOLDER = Symbol("PLACEHOLDER")
- @SharedImmutable
- private val REMOVED = Symbol("PLACEHOLDER")
-
- private infix fun Long.wo(other: Long) = this and other.inv()
- private fun Long.updateHead(newHead: Int) = (this wo HEAD_MASK) or (newHead.toLong() shl HEAD_SHIFT)
- private fun Long.updateTail(newTail: Int) = (this wo TAIL_MASK) or (newTail.toLong() shl TAIL_SHIFT)
-
- private inline fun <T> Long.withState(block: (head: Int, tail: Int) -> T): T {
- val head = ((this and HEAD_MASK) shr HEAD_SHIFT).toInt()
- val tail = ((this and TAIL_MASK) shr TAIL_SHIFT).toInt()
- return block(head, tail)
- }
-
- // FROZEN | CLOSED
- private fun Long.addFailReason(): Int = if (this and CLOSED_MASK != 0L) ADD_CLOSED else ADD_FROZEN
- }
-}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
index 1c12140..fbed546 100644
--- a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
+++ b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
@@ -4,9 +4,17 @@
package kotlinx.coroutines.internal
-@Suppress("ACTUAL_WITHOUT_EXPECT") // visibility
-internal actual typealias SynchronizedObject = Any
+import kotlinx.coroutines.*
-@PublishedApi
-internal actual inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T =
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual typealias SynchronizedObject = Any
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@InternalCoroutinesApi
+public actual inline fun <T> synchronized(lock: SynchronizedObject, block: () -> T): T =
block()
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/native/src/internal/ThreadSafeHeap.kt b/kotlinx-coroutines-core/native/src/internal/ThreadSafeHeap.kt
index 7787a1d..f966c999 100644
--- a/kotlinx-coroutines-core/native/src/internal/ThreadSafeHeap.kt
+++ b/kotlinx-coroutines-core/native/src/internal/ThreadSafeHeap.kt
@@ -1,134 +1,10 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.internal
-import kotlinx.coroutines.*
-
-/**
- * @suppress **This is unstable API and it is subject to change.**
- */
-public interface ThreadSafeHeapNode {
- public var index: Int
-}
-
-/**
- * Binary heap.
- *
- * @suppress **This is unstable API and it is subject to change.**
- */
-public class ThreadSafeHeap<T> where T: ThreadSafeHeapNode, T: Comparable<T> {
- private var a: Array<T?>? = null
-
- internal var size = 0
-
- public val isEmpty: Boolean get() = size == 0
-
- public fun peek(): T? = firstImpl()
-
- public fun removeFirstOrNull(): T? =
- if (size > 0) {
- removeAtImpl(0)
- } else
- null
-
- public inline fun removeFirstIf(predicate: (T) -> Boolean): T? {
- val first = firstImpl() ?: return null
- return if (predicate(first)) {
- removeAtImpl(0)
- } else
- null
- }
-
- public fun addLast(node: T) {
- addImpl(node)
- }
-
- public fun addLastIf(node: T, cond: () -> Boolean): Boolean =
- if (cond()) {
- addImpl(node)
- true
- } else
- false
-
- public fun remove(node: T): Boolean =
- if (node.index < 0) {
- false
- } else {
- removeAtImpl(node.index)
- true
- }
-
- @PublishedApi
- internal fun firstImpl(): T? = a?.get(0)
-
- @PublishedApi
- internal fun removeAtImpl(index: Int): T {
- check(size > 0)
- val a = this.a!!
- size--
- if (index < size) {
- swap(index, size)
- val j = (index - 1) / 2
- if (index > 0 && a[index]!! < a[j]!!) {
- swap(index, j)
- siftUpFrom(j)
- } else {
- siftDownFrom(index)
- }
- }
- val result = a[size]!!
- result.index = -1
- a[size] = null
- return result
- }
-
- @PublishedApi
- internal fun addImpl(node: T) {
- val a = realloc()
- var i = size++
- a[i] = node
- node.index = i
- siftUpFrom(i)
- }
-
- private tailrec fun siftUpFrom(i: Int) {
- if (i <= 0) return
- val a = a!!
- val j = (i - 1) / 2
- if (a[j]!! <= a[i]!!) return
- swap(i, j)
- siftUpFrom(j)
- }
-
- private tailrec fun siftDownFrom(i: Int) {
- var j = 2 * i + 1
- if (j >= size) return
- val a = a!!
- if (j + 1 < size && a[j + 1]!! < a[j]!!) j++
- if (a[i]!! <= a[j]!!) return
- swap(i, j)
- siftDownFrom(j)
- }
-
- @Suppress("UNCHECKED_CAST")
- private fun realloc(): Array<T?> {
- val a = this.a
- return when {
- a == null -> (arrayOfNulls<ThreadSafeHeapNode>(4) as Array<T?>).also { this.a = it }
- size >= a.size -> a.copyOf(size * 2).also { this.a = it }
- else -> a
- }
- }
-
- private fun swap(i: Int, j: Int) {
- val a = a!!
- val ni = a[j]!!
- val nj = a[i]!!
- a[i] = ni
- a[j] = nj
- ni.index = i
- nj.index = j
- }
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun <T> clear(a: Array<T?>) {
+ for (i in a.indices) a[i] = null
}
\ No newline at end of file
diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md
index 6e4d6d5..2286927 100644
--- a/kotlinx-coroutines-debug/README.md
+++ b/kotlinx-coroutines-debug/README.md
@@ -18,7 +18,7 @@
Add `kotlinx-coroutines-debug` to your project test dependencies:
```
dependencies {
- testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.0-M2'
+ testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.0-RC'
}
```
@@ -57,7 +57,7 @@
### Using as JVM agent
It is possible to use this module as a standalone JVM agent to enable debug probes on the application startup.
-You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.3.0-M2.jar`.
+You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.3.0-RC.jar`.
Additionally, on Linux and Mac OS X you can use `kill -5 $pid` command in order to force your application to print all alive coroutines.
diff --git a/kotlinx-coroutines-debug/build.gradle b/kotlinx-coroutines-debug/build.gradle
index e96cc6c..296566b 100644
--- a/kotlinx-coroutines-debug/build.gradle
+++ b/kotlinx-coroutines-debug/build.gradle
@@ -1,3 +1,5 @@
+import org.w3c.dom.Element
+
/*
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@@ -16,6 +18,35 @@
attributes "Can-Redefine-Classes": "true"
}
}
+/*
+ * It is possible to extend a particular configuration with shadow,
+ * but in that case it changes dependency type to "runtime" and resolves it
+ * (so it cannot be further modified). Otherwise, shadow just ignores all dependencies.
+ */
+configurations.shadow.extendsFrom(configurations.compile)
+
+/*
+ * Thus we are rewriting the POM. I am really question my existence at this point.
+ */
+project.ext.configureMavenDependencies = {
+ def root = it.asElement() as Element
+ def dependencies = root.getChildNodes().find { it.nodeName == "dependencies" }.childNodes
+ def childrenToRemove = []
+ for (i in 0..dependencies.length - 1) {
+ def dependency = dependencies.item(i) as Element
+ def scope = dependency.getChildNodes().find { it.nodeName == "scope" } as Element
+ def groupId = dependency.getChildNodes().find { it.nodeName == "groupId" } as Element
+ if (groupId != null && groupId.firstChild.nodeValue == "net.bytebuddy") {
+ childrenToRemove.add(dependency)
+ } else if (scope != null) {
+ scope.firstChild.setNodeValue("compile")
+ }
+ }
+
+ childrenToRemove.each {
+ root.getChildNodes().find { it.nodeName == "dependencies" }.removeChild(it)
+ }
+}
shadowJar {
classifier null
diff --git a/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt
index 7208294..3ccbe0a 100644
--- a/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt
+++ b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt
@@ -70,6 +70,7 @@
fun testStackMergeEscapeSuspendMethod() = runTest {
launchEscapingCoroutine()
awaitCoroutineStarted()
+ Thread.sleep(10)
verifyDump(
"Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored
"Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" +
diff --git a/kotlinx-coroutines-test/README.md b/kotlinx-coroutines-test/README.md
index a8bbafc..1838bbf 100644
--- a/kotlinx-coroutines-test/README.md
+++ b/kotlinx-coroutines-test/README.md
@@ -9,7 +9,7 @@
Add `kotlinx-coroutines-test` to your project test dependencies:
```
dependencies {
- testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.0-M2'
+ testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.0-RC'
}
```
diff --git a/reactive/coroutines-guide-reactive.md b/reactive/coroutines-guide-reactive.md
index 5942326..0eff27b 100644
--- a/reactive/coroutines-guide-reactive.md
+++ b/reactive/coroutines-guide-reactive.md
@@ -1,6 +1,6 @@
<!--- INCLUDE .*/example-reactive-([a-z]+)-([0-9]+)\.kt
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
@@ -617,7 +617,7 @@
context: CoroutineContext, // the context to execute this coroutine in
predicate: (T) -> Boolean, // the filter predicate
mapper: (T) -> R // the mapper function
-) = GlobalScope.publish<R>(context) {
+) = publish<R>(context) {
collect { // collect the source stream
if (predicate(it)) // filter part
send(mapper(it)) // map part
@@ -638,7 +638,7 @@
```kotlin
fun main() = runBlocking<Unit> {
range(1, 5)
- .fusedFilterMap(coroutineContext, { it % 2 == 0}, { "$it is even" })
+ .fusedFilterMap(Dispatchers.Unconfined, { it % 2 == 0}, { "$it is even" })
.collect { println(it) } // print all the resulting strings
}
```
@@ -673,7 +673,7 @@
-->
```kotlin
-fun <T, U> Publisher<T>.takeUntil(context: CoroutineContext, other: Publisher<U>) = GlobalScope.publish<T>(context) {
+fun <T, U> Publisher<T>.takeUntil(context: CoroutineContext, other: Publisher<U>) = publish<T>(context) {
this@takeUntil.openSubscription().consume { // explicitly open channel to Publisher<T>
val current = this
other.openSubscription().consume { // explicitly open channel to Publisher<U>
@@ -711,7 +711,7 @@
fun main() = runBlocking<Unit> {
val slowNums = rangeWithInterval(200, 1, 10) // numbers with 200ms interval
val stop = rangeWithInterval(500, 1, 10) // the first one after 500ms
- slowNums.takeUntil(coroutineContext, stop).collect { println(it) } // let's test it
+ slowNums.takeUntil(Dispatchers.Unconfined, stop).collect { println(it) } // let's test it
}
```
@@ -742,7 +742,7 @@
-->
```kotlin
-fun <T> Publisher<Publisher<T>>.merge(context: CoroutineContext) = GlobalScope.publish<T>(context) {
+fun <T> Publisher<Publisher<T>>.merge(context: CoroutineContext) = publish<T>(context) {
collect { pub -> // for each publisher collected
launch { // launch a child coroutine
pub.collect { send(it) } // resend all element from this publisher
@@ -783,7 +783,7 @@
```kotlin
fun main() = runBlocking<Unit> {
- testPub().merge(coroutineContext).collect { println(it) } // print the whole stream
+ testPub().merge(Dispatchers.Unconfined).collect { println(it) } // print the whole stream
}
```
@@ -865,7 +865,7 @@
-->
```kotlin
-fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = GlobalScope.publish<Int>(context) {
+fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = publish<Int>(context) {
for (x in start until start + count) {
delay(time) // wait before sending each number
send(x)
@@ -915,7 +915,7 @@
-->
```kotlin
-fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = GlobalScope.publish<Int>(context) {
+fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = publish<Int>(context) {
for (x in start until start + count) {
delay(time) // wait before sending each number
send(x)
@@ -1067,12 +1067,12 @@
[whileSelect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/while-select.html
<!--- MODULE kotlinx-coroutines-reactive -->
<!--- INDEX kotlinx.coroutines.reactive -->
-[publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/kotlinx.coroutines.-coroutine-scope/publish.html
+[publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/publish.html
[org.reactivestreams.Publisher.collect]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/collect.html
[org.reactivestreams.Publisher.openSubscription]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/open-subscription.html
<!--- MODULE kotlinx-coroutines-rx2 -->
<!--- INDEX kotlinx.coroutines.rx2 -->
-[rxFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/kotlinx.coroutines.-coroutine-scope/rx-flowable.html
+[rxFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-flowable.html
<!--- END -->
diff --git a/reactive/kotlinx-coroutines-reactive/README.md b/reactive/kotlinx-coroutines-reactive/README.md
index d7746e1..69691e8 100644
--- a/reactive/kotlinx-coroutines-reactive/README.md
+++ b/reactive/kotlinx-coroutines-reactive/README.md
@@ -33,7 +33,7 @@
[ReceiveChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/index.html
<!--- MODULE kotlinx-coroutines-reactive -->
<!--- INDEX kotlinx.coroutines.reactive -->
-[publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/kotlinx.coroutines.-coroutine-scope/publish.html
+[publish]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/publish.html
[org.reactivestreams.Publisher.awaitFirst]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/await-first.html
[org.reactivestreams.Publisher.awaitFirstOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/await-first-or-default.html
[org.reactivestreams.Publisher.awaitFirstOrElse]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactive/kotlinx.coroutines.reactive/org.reactivestreams.-publisher/await-first-or-else.html
diff --git a/reactive/kotlinx-coroutines-reactive/src/Convert.kt b/reactive/kotlinx-coroutines-reactive/src/Convert.kt
index 2be24af..a7ae128 100644
--- a/reactive/kotlinx-coroutines-reactive/src/Convert.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/Convert.kt
@@ -21,7 +21,7 @@
* @param context -- the coroutine context from which the resulting observable is going to be signalled
*/
@ObsoleteCoroutinesApi
-public fun <T> ReceiveChannel<T>.asPublisher(context: CoroutineContext = EmptyCoroutineContext): Publisher<T> = GlobalScope.publish(context) {
+public fun <T> ReceiveChannel<T>.asPublisher(context: CoroutineContext = EmptyCoroutineContext): Publisher<T> = publish(context) {
for (t in this@asPublisher)
send(t)
}
diff --git a/reactive/kotlinx-coroutines-reactive/src/Publish.kt b/reactive/kotlinx-coroutines-reactive/src/Publish.kt
index f5ea01e..a7d5387 100644
--- a/reactive/kotlinx-coroutines-reactive/src/Publish.kt
+++ b/reactive/kotlinx-coroutines-reactive/src/Publish.kt
@@ -1,7 +1,9 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
package kotlinx.coroutines.reactive
import kotlinx.atomicfu.*
@@ -11,6 +13,7 @@
import kotlinx.coroutines.sync.*
import org.reactivestreams.*
import kotlin.coroutines.*
+import kotlin.internal.LowPriorityInOverloadResolution
/**
* Creates cold reactive [Publisher] that runs a given [block] in a coroutine.
@@ -26,25 +29,44 @@
* | Normal completion or `close` without cause | `onComplete`
* | Failure with exception or `close` with cause | `onError`
*
- * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * Coroutine context can be specified with [context] argument.
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
- * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
- * with corresponding [coroutineContext] element.
+ * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
*
* **Note: This is an experimental api.** Behaviour of publishers that work as children in a parent scope with respect
* to cancellation and error handling may change in the future.
- *
- * @param context context of the coroutine.
- * @param block the coroutine code.
*/
@ExperimentalCoroutinesApi
+public fun <T> publish(
+ context: CoroutineContext = EmptyCoroutineContext,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Publisher<T> {
+ require(context[Job] === null) { "Publisher context cannot contain job in it." +
+ "Its lifecycle should be managed via subscription. Had $context" }
+ return publishInternal(GlobalScope, context, block)
+}
+
+@Deprecated(
+ message = "CoroutineScope.publish is deprecated in favour of top-level publish",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("publish(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0. Binary compatibility with Spring
+@LowPriorityInOverloadResolution
public fun <T> CoroutineScope.publish(
context: CoroutineContext = EmptyCoroutineContext,
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Publisher<T> = publishInternal(this, context, block)
+
+/** @suppress For internal use from other reactive integration modules only */
+@InternalCoroutinesApi
+public fun <T> publishInternal(
+ scope: CoroutineScope, // support for legacy publish in scope
+ context: CoroutineContext,
+ block: suspend ProducerScope<T>.() -> Unit
): Publisher<T> = Publisher { subscriber ->
// specification requires NPE on null subscriber
if (subscriber == null) throw NullPointerException("Subscriber cannot be null")
- val newContext = newCoroutineContext(context)
+ val newContext = scope.newCoroutineContext(context)
val coroutine = PublisherCoroutine(newContext, subscriber)
subscriber.onSubscribe(coroutine) // do it first (before starting coroutine), to avoid unnecessary suspensions
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
@@ -54,7 +76,8 @@
private const val SIGNALLED = -2L // already signalled subscriber onCompleted/onError
@Suppress("CONFLICTING_JVM_DECLARATIONS", "RETURN_TYPE_MISMATCH_ON_INHERITANCE")
-private class PublisherCoroutine<in T>(
+@InternalCoroutinesApi
+public class PublisherCoroutine<in T>(
parentContext: CoroutineContext,
private val subscriber: Subscriber<T>
) : AbstractCoroutine<Unit>(parentContext, true), ProducerScope<T>, Subscription, SelectClause2<T, SendChannel<T>> {
@@ -138,16 +161,16 @@
}
// now update nRequested
while (true) { // lock-free loop on nRequested
- val cur = _nRequested.value
- if (cur < 0) break // closed from inside onNext => unlock
- if (cur == Long.MAX_VALUE) break // no back-pressure => unlock
- val upd = cur - 1
- if (_nRequested.compareAndSet(cur, upd)) {
- if (upd == 0L) {
+ val current = _nRequested.value
+ if (current < 0) break // closed from inside onNext => unlock
+ if (current == Long.MAX_VALUE) break // no back-pressure => unlock
+ val updated = current - 1
+ if (_nRequested.compareAndSet(current, updated)) {
+ if (updated == 0L) {
// return to keep locked due to back-pressure
return
}
- break // unlock if upd > 0
+ break // unlock if updated > 0
}
}
unlockAndCheckCompleted()
@@ -176,17 +199,31 @@
if (cancelled) {
// If the parent had failed to handle our exception, then we must not lose this exception
if (cause != null && !handled) handleCoroutineException(context, cause)
- } else {
- try {
- if (cause != null && cause !is CancellationException) {
- subscriber.onError(cause)
+ return
+ }
+
+ try {
+ if (cause != null && cause !is CancellationException) {
+ /*
+ * Reactive frameworks have two types of exceptions: regular and fatal.
+ * Regular are passed to onError.
+ * Fatal can be passed to onError, but even the standard implementations **can just swallow it** (e.g. see #1297).
+ * Such behaviour is inconsistent, leads to silent failures and we can't possibly know whether
+ * the cause will be handled by onError (and moreover, it depends on whether a fatal exception was
+ * thrown by subscriber or upstream).
+ * To make behaviour consistent and least surprising, we always handle fatal exceptions
+ * by coroutines machinery, anyway, they should not be present in regular program flow,
+ * thus our goal here is just to expose it as soon as possible.
+ */
+ subscriber.onError(cause)
+ if (!handled && cause.isFatal()) {
+ handleCoroutineException(context, cause)
}
- else {
- subscriber.onComplete()
- }
- } catch (e: Throwable) {
- handleCoroutineException(context, e)
+ } else {
+ subscriber.onComplete()
}
+ } catch (e: Throwable) {
+ handleCoroutineException(context, e)
}
}
} finally {
@@ -220,12 +257,12 @@
// assert: isCompleted
private fun signalCompleted(cause: Throwable?, handled: Boolean) {
while (true) { // lock-free loop for nRequested
- val cur = _nRequested.value
- if (cur == SIGNALLED) return // some other thread holding lock already signalled cancellation/completion
- check(cur >= 0) // no other thread could have marked it as CLOSED, because onCompleted[Exceptionally] is invoked once
- if (!_nRequested.compareAndSet(cur, CLOSED)) continue // retry on failed CAS
+ val current = _nRequested.value
+ if (current == SIGNALLED) return // some other thread holding lock already signalled cancellation/completion
+ check(current >= 0) // no other thread could have marked it as CLOSED, because onCompleted[Exceptionally] is invoked once
+ if (!_nRequested.compareAndSet(current, CLOSED)) continue // retry on failed CAS
// Ok -- marked as CLOSED, now can unlock the mutex if it was locked due to backpressure
- if (cur == 0L) {
+ if (current == 0L) {
doLockedSignalCompleted(cause, handled)
} else {
// otherwise mutex was either not locked or locked in concurrent onNext... try lock it to signal completion
@@ -250,4 +287,6 @@
cancelled = true
super.cancel(null)
}
+
+ private fun Throwable.isFatal() = this is VirtualMachineError || this is ThreadDeath || this is LinkageError
}
diff --git a/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt b/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt
index ca18349..aaeaa00 100644
--- a/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/IntegrationTest.kt
@@ -20,7 +20,7 @@
) : TestBase() {
enum class Ctx {
- MAIN { override fun invoke(context: CoroutineContext): CoroutineContext = context },
+ MAIN { override fun invoke(context: CoroutineContext): CoroutineContext = context.minusKey(Job) },
DEFAULT { override fun invoke(context: CoroutineContext): CoroutineContext = Dispatchers.Default },
UNCONFINED { override fun invoke(context: CoroutineContext): CoroutineContext = Dispatchers.Unconfined };
@@ -39,7 +39,7 @@
@Test
fun testEmpty(): Unit = runBlocking {
- val pub = CoroutineScope(ctx(coroutineContext)).publish<String> {
+ val pub = publish<String>(ctx(coroutineContext)) {
if (delay) delay(1)
// does not send anything
}
@@ -77,7 +77,7 @@
@Test
fun testNumbers() = runBlocking<Unit> {
val n = 100 * stressTestMultiplier
- val pub = CoroutineScope(ctx(coroutineContext)).publish {
+ val pub = publish(ctx(coroutineContext)) {
for (i in 1..n) {
send(i)
if (delay) delay(1)
@@ -99,8 +99,7 @@
fun testCancelWithoutValue() = runTest {
val job = launch(Job(), start = CoroutineStart.UNDISPATCHED) {
publish<String> {
- yield()
- expectUnreached()
+ hang {}
}.awaitFirst()
}
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublishParentCancelStressTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublishParentCancelStressTest.kt
deleted file mode 100644
index 5936712..0000000
--- a/reactive/kotlinx-coroutines-reactive/test/PublishParentCancelStressTest.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.reactive
-
-import kotlinx.coroutines.*
-import org.junit.*
-import org.junit.Test
-import org.reactivestreams.*
-import java.util.concurrent.*
-import kotlin.test.*
-
-public class PublishParentCancelStressTest : TestBase() {
- private val dispatcher = newFixedThreadPoolContext(3, "PublishParentCancelStressTest")
- private val N_TIMES = 5000 * stressTestMultiplier
-
- @After
- fun tearDown() {
- dispatcher.close()
- }
-
- @Test
- fun testStress() = runTest {
- var unhandled: Throwable? = null
- val handler = CoroutineExceptionHandler { _, ex -> unhandled = ex }
- repeat(N_TIMES) {
- val barrier = CyclicBarrier(4)
- // launch parent job for publisher
- val parent = GlobalScope.async<Unit>(dispatcher + handler) {
- val publisher = publish<Unit> {
- // BARRIER #1 - child publisher crashes
- barrier.await()
- throw TestException()
- }
- var sub: Subscription? = null
- publisher.subscribe(object : Subscriber<Unit> {
- override fun onComplete() { error("Cannot be reached") }
- override fun onSubscribe(s: Subscription?) { sub = s }
- override fun onNext(t: Unit?) { error("Cannot be reached" ) }
- override fun onError(t: Throwable?) {
- assertTrue(t is TestException)
- }
- })
- launch {
- // BARRIER #3 -- cancel subscription
- barrier.await()
- sub!!.cancel()
- }
- // BARRIER #2 -- parent completes
- barrier.await()
- Unit
- }
- // BARRIE #4 - go 1-3 together
- barrier.await()
- // Make sure exception is not lost, but incorporated into parent
- val result = kotlin.runCatching { parent.await() }
- assertTrue(result.exceptionOrNull() is TestException)
- // Make sure unhandled exception handler was not invoked
- assertNull(unhandled)
- }
- }
-
- private class TestException : Exception()
-}
\ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt
index e022ff1..4ffa074 100644
--- a/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/PublishTest.kt
@@ -14,7 +14,7 @@
@Test
fun testBasicEmpty() = runTest {
expect(1)
- val publisher = publish<Int> {
+ val publisher = publish<Int>(currentDispatcher()) {
expect(5)
}
expect(2)
@@ -32,7 +32,7 @@
@Test
fun testBasicSingle() = runTest {
expect(1)
- val publisher = publish {
+ val publisher = publish(currentDispatcher()) {
expect(5)
send(42)
expect(7)
@@ -58,7 +58,7 @@
@Test
fun testBasicError() = runTest {
expect(1)
- val publisher = publish<Int>(NonCancellable) {
+ val publisher = publish<Int>(currentDispatcher()) {
expect(5)
throw RuntimeException("OK")
}
@@ -82,23 +82,14 @@
}
@Test
- fun testCancelsParentOnFailure() = runTest(
- expected = { it is RuntimeException && it.message == "OK" }
- ) {
- // has parent, so should cancel it on failure
- publish<Unit> {
- throw RuntimeException("OK")
- }.openSubscription()
- }
-
- @Test
- fun testHandleFailureAfterCancel() = runTest(
- unhandled = listOf({ it -> it is RuntimeException && it.message == "FAILED" })
- ){
+ fun testHandleFailureAfterCancel() = runTest {
expect(1)
- // Exception should be delivered to CoroutineExceptionHandler, because we create publisher
- // with the NonCancellable parent
- val publisher = publish<Unit>(NonCancellable + Dispatchers.Unconfined) {
+
+ val eh = CoroutineExceptionHandler { _, t ->
+ assertTrue(t is RuntimeException)
+ expect(6)
+ }
+ val publisher = publish<Unit>(Dispatchers.Unconfined + eh) {
try {
expect(3)
delay(10000)
@@ -128,95 +119,13 @@
})
expect(4)
sub!!.cancel()
- finish(6)
- }
-
- @Test
- fun testParentHandlesFailure() = runTest {
- expect(1)
- val deferred = CompletableDeferred<Unit>()
- val publisher = publish<Unit>(deferred + Dispatchers.Unconfined) {
- try {
- expect(3)
- delay(10000)
- } finally {
- expect(5)
- throw TestException("FAILED")
- }
- }
- var sub: Subscription? = null
- publisher.subscribe(object : Subscriber<Unit> {
- override fun onComplete() {
- expectUnreached()
- }
-
- override fun onSubscribe(s: Subscription) {
- expect(2)
- sub = s
- }
-
- override fun onNext(t: Unit?) {
- expectUnreached()
- }
-
- override fun onError(t: Throwable?) {
- expectUnreached()
- }
- })
- expect(4)
- sub!!.cancel()
-
- try {
- deferred.await()
- expectUnreached()
- } catch (e: TestException) {
- expect(6)
- }
-
finish(7)
}
@Test
- fun testPublishFailureCancelsParent() = runTest(
- expected = { it is TestException }
- ) {
- expect(1)
- val publisher = publish<Unit> {
- expect(5)
- throw TestException()
- }
- expect(2)
- publisher.subscribe(object : Subscriber<Unit> {
- override fun onComplete() {
- expectUnreached()
- }
-
- override fun onSubscribe(s: Subscription) {
- expect(3)
- }
-
- override fun onNext(t: Unit?) {
- expectUnreached()
- }
-
- override fun onError(t: Throwable?) {
- assertTrue(t is TestException)
- expect(6)
- }
- })
- expect(4)
- try {
- yield() // to coroutine, will crash because it is a cancelled parent coroutine
- } finally {
- finish(7)
- }
- expectUnreached()
- }
-
- @Test
fun testOnNextError() = runTest {
expect(1)
- val publisher = publish<String>(NonCancellable) {
+ val publisher = publish(currentDispatcher()) {
expect(4)
try {
send("OK")
@@ -255,7 +164,7 @@
@Test
fun testFailingConsumer() = runTest {
- val pub = publish {
+ val pub = publish(currentDispatcher()) {
repeat(3) {
expect(it + 1) // expect(1), expect(2) *should* be invoked
send(it)
@@ -269,4 +178,9 @@
finish(3)
}
}
+
+ @Test
+ fun testIllegalArgumentException() {
+ assertFailsWith<IllegalArgumentException> { publish<Int>(Job()) { } }
+ }
}
\ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublisherBackpressureTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublisherBackpressureTest.kt
index 503a015..258632b 100644
--- a/reactive/kotlinx-coroutines-reactive/test/PublisherBackpressureTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/PublisherBackpressureTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.reactive
@@ -12,7 +12,7 @@
@Test
fun testCancelWhileBPSuspended() = runBlocking {
expect(1)
- val observable = publish {
+ val observable = publish(currentDispatcher()) {
expect(5)
send("A") // will not suspend, because an item was requested
expect(7)
diff --git a/reactive/kotlinx-coroutines-reactive/test/PublisherMultiTest.kt b/reactive/kotlinx-coroutines-reactive/test/PublisherMultiTest.kt
index cac2f55..e238d39 100644
--- a/reactive/kotlinx-coroutines-reactive/test/PublisherMultiTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/PublisherMultiTest.kt
@@ -13,7 +13,7 @@
@Test
fun testConcurrentStress() = runBlocking {
val n = 10_000 * stressTestMultiplier
- val observable = GlobalScope.publish {
+ val observable = publish {
// concurrent emitters (many coroutines)
val jobs = List(n) {
// launch
diff --git a/reactive/kotlinx-coroutines-reactive/test/ReactiveStreamTckTest.kt b/reactive/kotlinx-coroutines-reactive/test/ReactiveStreamTckTest.kt
index 2e55b8a..6816a98 100644
--- a/reactive/kotlinx-coroutines-reactive/test/ReactiveStreamTckTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/ReactiveStreamTckTest.kt
@@ -26,29 +26,17 @@
private val dispatcher: Dispatcher
) : PublisherVerification<Long>(TestEnvironment(500, 500)) {
- private val scope = CoroutineScope(dispatcher.dispatcher + NonCancellable)
-
override fun createPublisher(elements: Long): Publisher<Long> =
- scope.publish {
+ publish(dispatcher.dispatcher) {
for (i in 1..elements) send(i)
}
override fun createFailedPublisher(): Publisher<Long> =
- scope.publish {
+ publish(dispatcher.dispatcher) {
throw TestException()
}
@Test
- public override fun required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber() {
- // This test fails on default dispatcher because it retains a reference to the last task
- // in the structure of its GlobalQueue
- // So we skip it with the default dispatcher.
- // todo: remove it when CoroutinesScheduler is improved
- if (dispatcher == Dispatcher.DEFAULT) return
- super.required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber()
- }
-
- @Test
public override fun optional_spec105_emptyStreamMustTerminateBySignallingOnComplete() {
throw SkipException("Skipped")
}
diff --git a/reactive/kotlinx-coroutines-reactive/test/flow/PublisherAsFlowTest.kt b/reactive/kotlinx-coroutines-reactive/test/flow/PublisherAsFlowTest.kt
index 74f5914..3f33b33 100644
--- a/reactive/kotlinx-coroutines-reactive/test/flow/PublisherAsFlowTest.kt
+++ b/reactive/kotlinx-coroutines-reactive/test/flow/PublisherAsFlowTest.kt
@@ -17,7 +17,7 @@
var onCancelled = 0
var onError = 0
- val publisher = publish {
+ val publisher = publish(currentDispatcher()) {
coroutineContext[Job]?.invokeOnCompletion {
if (it is CancellationException) ++onCancelled
}
@@ -45,7 +45,7 @@
@Test
fun testBufferSize1() = runTest {
- val publisher = publish {
+ val publisher = publish(currentDispatcher()) {
expect(1)
send(3)
@@ -66,7 +66,7 @@
@Test
fun testBufferSize10() = runTest {
- val publisher = publish {
+ val publisher = publish(currentDispatcher()) {
expect(1)
send(5)
@@ -87,7 +87,7 @@
@Test
fun testConflated() = runTest {
- val publisher = publish {
+ val publisher = publish(currentDispatcher()) {
for (i in 1..5) send(i)
}
val list = publisher.asFlow().conflate().toList()
@@ -96,7 +96,7 @@
@Test
fun testProduce() = runTest {
- val flow = publish { repeat(10) { send(it) } }.asFlow()
+ val flow = publish(currentDispatcher()) { repeat(10) { send(it) } }.asFlow()
check((0..9).toList(), flow.produceIn(this))
check((0..9).toList(), flow.buffer(2).produceIn(this))
check((0..9).toList(), flow.buffer(Channel.UNLIMITED).produceIn(this))
@@ -113,7 +113,7 @@
fun testProduceCancellation() = runTest {
expect(1)
// publisher is an async coroutine, so it overproduces to the channel, but still gets cancelled
- val flow = publish {
+ val flow = publish(currentDispatcher()) {
expect(3)
repeat(10) { value ->
when (value) {
diff --git a/reactive/kotlinx-coroutines-reactor/README.md b/reactive/kotlinx-coroutines-reactor/README.md
index 1a08834..1531488 100644
--- a/reactive/kotlinx-coroutines-reactor/README.md
+++ b/reactive/kotlinx-coroutines-reactor/README.md
@@ -29,8 +29,8 @@
<!--- INDEX kotlinx.coroutines.channels -->
<!--- MODULE kotlinx-coroutines-reactor -->
<!--- INDEX kotlinx.coroutines.reactor -->
-[mono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/kotlinx.coroutines.-coroutine-scope/mono.html
-[flux]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/kotlinx.coroutines.-coroutine-scope/flux.html
+[mono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/mono.html
+[flux]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/flux.html
[kotlinx.coroutines.Job.asMono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/kotlinx.coroutines.-job/as-mono.html
[kotlinx.coroutines.Deferred.asMono]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/kotlinx.coroutines.-deferred/as-mono.html
[kotlinx.coroutines.channels.ReceiveChannel.asFlux]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-reactor/kotlinx.coroutines.reactor/kotlinx.coroutines.channels.-receive-channel/as-flux.html
diff --git a/reactive/kotlinx-coroutines-reactor/build.gradle b/reactive/kotlinx-coroutines-reactor/build.gradle
index 72ef6e5..c73716d 100644
--- a/reactive/kotlinx-coroutines-reactor/build.gradle
+++ b/reactive/kotlinx-coroutines-reactor/build.gradle
@@ -12,4 +12,12 @@
url = new URL("https://projectreactor.io/docs/core/$reactor_vesion/api/")
packageListUrl = projectDir.toPath().resolve("package.list").toUri().toURL()
}
+}
+
+compileTestKotlin {
+ kotlinOptions.jvmTarget = "1.8"
+}
+
+compileKotlin {
+ kotlinOptions.jvmTarget = "1.8"
}
\ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/src/Convert.kt b/reactive/kotlinx-coroutines-reactor/src/Convert.kt
index ea9f882..cf6b65d 100644
--- a/reactive/kotlinx-coroutines-reactor/src/Convert.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/Convert.kt
@@ -22,7 +22,7 @@
* @param context -- the coroutine context from which the resulting mono is going to be signalled
*/
@ExperimentalCoroutinesApi
-public fun Job.asMono(context: CoroutineContext): Mono<Unit> = GlobalScope.mono(context) { this@asMono.join() }
+public fun Job.asMono(context: CoroutineContext): Mono<Unit> = mono(context) { this@asMono.join() }
/**
* Converts this deferred value to the hot reactive mono that signals
* [success][MonoSink.success] or [error][MonoSink.error].
@@ -36,7 +36,7 @@
* @param context -- the coroutine context from which the resulting mono is going to be signalled
*/
@ExperimentalCoroutinesApi
-public fun <T> Deferred<T?>.asMono(context: CoroutineContext): Mono<T> = GlobalScope.mono(context) { this@asMono.await() }
+public fun <T> Deferred<T?>.asMono(context: CoroutineContext): Mono<T> = mono(context) { this@asMono.await() }
/**
* Converts a stream of elements received from the channel to the hot reactive flux.
@@ -50,7 +50,7 @@
* @param context -- the coroutine context from which the resulting flux is going to be signalled
*/
@ObsoleteCoroutinesApi
-public fun <T> ReceiveChannel<T>.asFlux(context: CoroutineContext = EmptyCoroutineContext): Flux<T> = GlobalScope.flux(context) {
+public fun <T> ReceiveChannel<T>.asFlux(context: CoroutineContext = EmptyCoroutineContext): Flux<T> = flux(context) {
for (t in this@asFlux)
send(t)
}
\ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/src/Flux.kt b/reactive/kotlinx-coroutines-reactor/src/Flux.kt
index 3495501..316146b 100644
--- a/reactive/kotlinx-coroutines-reactor/src/Flux.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/Flux.kt
@@ -1,24 +1,28 @@
+
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
package kotlinx.coroutines.reactor
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.reactive.*
+import org.reactivestreams.Publisher
+import reactor.core.CoreSubscriber
import reactor.core.publisher.*
import kotlin.coroutines.*
+import kotlin.internal.LowPriorityInOverloadResolution
/**
* Creates cold reactive [Flux] that runs a given [block] in a coroutine.
* Every time the returned flux is subscribed, it starts a new coroutine in the specified [context].
* Coroutine emits items with `send`. Unsubscribing cancels running coroutine.
*
- * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * Coroutine context can be specified with [context] argument.
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
- * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
- * with corresponding [coroutineContext] element.
*
* Invocations of `send` are suspended appropriately when subscribers apply back-pressure and to ensure that
* `onNext` is not invoked concurrently.
@@ -29,12 +33,45 @@
* | Normal completion or `close` without cause | `onComplete`
* | Failure with exception or `close` with cause | `onError`
*
+ * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
+ *
* **Note: This is an experimental api.** Behaviour of publishers that work as children in a parent scope with respect
* to cancellation and error handling may change in the future.
*/
@ExperimentalCoroutinesApi
-fun <T> CoroutineScope.flux(
+public fun <T> flux(
+ context: CoroutineContext = EmptyCoroutineContext,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Flux<T> {
+ require(context[Job] === null) { "Flux context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return Flux.from(reactorPublish(GlobalScope, context, block))
+}
+
+@Deprecated(
+ message = "CoroutineScope.flux is deprecated in favour of top-level flux",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("flux(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0. Binary compatibility with Spring
+@LowPriorityInOverloadResolution
+public fun <T> CoroutineScope.flux(
context: CoroutineContext = EmptyCoroutineContext,
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
): Flux<T> =
- Flux.from(publish(newCoroutineContext(context), block = block))
+ Flux.from(reactorPublish(this, context, block))
+
+private fun <T> reactorPublish(
+ scope: CoroutineScope,
+ context: CoroutineContext = EmptyCoroutineContext,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Publisher<T> = Publisher { subscriber ->
+ // specification requires NPE on null subscriber
+ if (subscriber == null) throw NullPointerException("Subscriber cannot be null")
+ require(subscriber is CoreSubscriber) { "Subscriber is not an instance of CoreSubscriber, context can not be extracted." }
+ val currentContext = subscriber.currentContext()
+ val reactorContext = (context[ReactorContext]?.context?.putAll(currentContext) ?: currentContext).asCoroutineContext()
+ val newContext = scope.newCoroutineContext(context + reactorContext)
+ val coroutine = PublisherCoroutine(newContext, subscriber)
+ subscriber.onSubscribe(coroutine) // do it first (before starting coroutine), to avoid unnecessary suspensions
+ coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
+}
diff --git a/reactive/kotlinx-coroutines-reactor/src/Mono.kt b/reactive/kotlinx-coroutines-reactor/src/Mono.kt
index 7174fb6..b218f6d 100644
--- a/reactive/kotlinx-coroutines-reactor/src/Mono.kt
+++ b/reactive/kotlinx-coroutines-reactor/src/Mono.kt
@@ -1,13 +1,16 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
package kotlinx.coroutines.reactor
import kotlinx.coroutines.*
import reactor.core.*
import reactor.core.publisher.*
import kotlin.coroutines.*
+import kotlin.internal.*
/**
* Creates cold [mono][Mono] that will run a given [block] in a coroutine.
@@ -20,19 +23,38 @@
* | Returns a null | `success`
* | Failure with exception or unsubscribe | `error`
*
- * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * Coroutine context can be specified with [context] argument.
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
- * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
- * with corresponding [coroutineContext] element.
*
- * @param context context of the coroutine.
- * @param block the coroutine code.
+ * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
*/
-fun <T> CoroutineScope.mono(
+public fun <T> mono(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T?
+): Mono<T> {
+ require(context[Job] === null) { "Mono context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return monoInternal(GlobalScope, context, block)
+}
+
+@Deprecated(
+ message = "CoroutineScope.mono is deprecated in favour of top-level mono",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("mono(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
+@LowPriorityInOverloadResolution
+public fun <T> CoroutineScope.mono(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> T?
+): Mono<T> = monoInternal(this, context, block)
+
+private fun <T> monoInternal(
+ scope: CoroutineScope, // support for legacy mono in scope
+ context: CoroutineContext,
+ block: suspend CoroutineScope.() -> T?
): Mono<T> = Mono.create { sink ->
- val newContext = newCoroutineContext(context)
+ val reactorContext = (context[ReactorContext]?.context?.putAll(sink.currentContext()) ?: sink.currentContext()).asCoroutineContext()
+ val newContext = scope.newCoroutineContext(context + reactorContext)
val coroutine = MonoCoroutine(newContext, sink)
sink.onDispose(coroutine)
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
@@ -57,7 +79,7 @@
handleCoroutineException(context, cause)
}
}
-
+
override fun dispose() {
disposed = true
cancel()
@@ -65,4 +87,3 @@
override fun isDisposed(): Boolean = disposed
}
-
diff --git a/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt b/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt
new file mode 100644
index 0000000..5a4ccd0
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/src/ReactorContext.kt
@@ -0,0 +1,44 @@
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import reactor.util.context.Context
+import kotlin.coroutines.*
+
+/**
+ * Wraps Reactor's [Context] into [CoroutineContext] element for seamless integration Reactor and kotlinx.coroutines.
+ *
+ * [Context.asCoroutineContext] is defined to add Reactor's [Context] elements as part of [CoroutineContext].
+ *
+ * Reactor builders [mono] and [flux] use this context element to enhance the resulting `subscriberContext`.
+ *
+ * ### Usages
+ * Passing reactor context from coroutine builder to reactor entity:
+ * ```
+ * launch(Context.of("key", "value").asCoroutineContext()) {
+ * mono {
+ * println(coroutineContext[ReactorContext]) // Prints { "key": "value" }
+ * }.subscribe()
+ * }
+ * ```
+ *
+ * Accessing modified reactor context enriched from the downstream:
+ * ```
+ * launch {
+ * mono {
+ * println(coroutineContext[ReactorContext]) // Prints { "key": "value" }
+ * }.subscriberContext(Context.of("key", "value"))
+ * .subscribe()
+ * }
+ * ```
+ */
+@ExperimentalCoroutinesApi
+public class ReactorContext(val context: Context) : AbstractCoroutineContextElement(ReactorContext) {
+ companion object Key : CoroutineContext.Key<ReactorContext>
+}
+
+/**
+ * Wraps the given [Context] into [ReactorContext], so it can be added to coroutine's context
+ * and later used via `coroutineContext[ReactorContext]`.
+ */
+@ExperimentalCoroutinesApi
+public fun Context.asCoroutineContext(): ReactorContext = ReactorContext(this)
\ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt b/reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt
index 9bd55cd..10e05b7 100644
--- a/reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/ConvertTest.kt
@@ -17,7 +17,7 @@
val job = launch {
expect(3)
}
- val mono = job.asMono(coroutineContext)
+ val mono = job.asMono(coroutineContext.minusKey(Job))
mono.subscribe {
expect(4)
}
@@ -29,11 +29,11 @@
@Test
fun testJobToMonoFail() = runBlocking {
expect(1)
- val job = async(NonCancellable) { // don't kill parent on exception
+ val job = async(NonCancellable) {
expect(3)
throw RuntimeException("OK")
}
- val mono = job.asMono(coroutineContext + NonCancellable)
+ val mono = job.asMono(coroutineContext.minusKey(Job))
mono.subscribe(
{ fail("no item should be emitted") },
{ expect(4) }
@@ -110,10 +110,10 @@
throw TestException("K")
}
val flux = c.asFlux(Dispatchers.Unconfined)
- val mono = GlobalScope.mono(Dispatchers.Unconfined) {
+ val mono = mono(Dispatchers.Unconfined) {
var result = ""
try {
- flux.consumeEach { result += it }
+ flux.collect { result += it }
} catch(e: Throwable) {
check(e is TestException)
result += e.message
diff --git a/reactive/kotlinx-coroutines-reactor/test/FluxMultiTest.kt b/reactive/kotlinx-coroutines-reactor/test/FluxMultiTest.kt
index c4c0dbc..ae23d3c 100644
--- a/reactive/kotlinx-coroutines-reactor/test/FluxMultiTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/FluxMultiTest.kt
@@ -15,7 +15,7 @@
@Test
fun testNumbers() {
val n = 100 * stressTestMultiplier
- val flux = GlobalScope.flux {
+ val flux = flux {
repeat(n) { send(it) }
}
checkMonoValue(flux.collectList()) { list ->
@@ -26,7 +26,7 @@
@Test
fun testConcurrentStress() {
val n = 10_000 * stressTestMultiplier
- val flux = GlobalScope.flux {
+ val flux = flux {
// concurrent emitters (many coroutines)
val jobs = List(n) {
// launch
@@ -45,7 +45,7 @@
@Test
fun testIteratorResendUnconfined() {
val n = 10_000 * stressTestMultiplier
- val flux = GlobalScope.flux(Dispatchers.Unconfined) {
+ val flux = flux(Dispatchers.Unconfined) {
Flux.range(0, n).collect { send(it) }
}
checkMonoValue(flux.collectList()) { list ->
@@ -56,7 +56,7 @@
@Test
fun testIteratorResendPool() {
val n = 10_000 * stressTestMultiplier
- val flux = GlobalScope.flux {
+ val flux = flux {
Flux.range(0, n).collect { send(it) }
}
checkMonoValue(flux.collectList()) { list ->
@@ -66,11 +66,11 @@
@Test
fun testSendAndCrash() {
- val flux = GlobalScope.flux {
+ val flux = flux {
send("O")
throw IOException("K")
}
- val mono = GlobalScope.mono {
+ val mono = mono {
var result = ""
try {
flux.consumeEach { result += it }
diff --git a/reactive/kotlinx-coroutines-reactor/test/FluxSingleTest.kt b/reactive/kotlinx-coroutines-reactor/test/FluxSingleTest.kt
index 241cc6a..7d8d469 100644
--- a/reactive/kotlinx-coroutines-reactor/test/FluxSingleTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/FluxSingleTest.kt
@@ -14,7 +14,7 @@
class FluxSingleTest {
@Test
fun testSingleNoWait() {
- val flux = GlobalScope.flux {
+ val flux = flux {
send("OK")
}
@@ -30,7 +30,7 @@
@Test
fun testSingleEmitAndAwait() {
- val flux = GlobalScope.flux {
+ val flux = flux {
send(Flux.just("O").awaitSingle() + "K")
}
@@ -41,7 +41,7 @@
@Test
fun testSingleWithDelay() {
- val flux = GlobalScope.flux {
+ val flux = flux {
send(Flux.just("O").delayElements(ofMillis(50)).awaitSingle() + "K")
}
@@ -52,7 +52,7 @@
@Test
fun testSingleException() {
- val flux = GlobalScope.flux {
+ val flux = flux {
send(Flux.just("O", "K").awaitSingle() + "K")
}
@@ -63,7 +63,7 @@
@Test
fun testAwaitFirst() {
- val flux = GlobalScope.flux {
+ val flux = flux {
send(Flux.just("O", "#").awaitFirst() + "K")
}
@@ -74,7 +74,7 @@
@Test
fun testAwaitFirstOrDefault() {
- val flux = GlobalScope.flux {
+ val flux = flux {
send(Flux.empty<String>().awaitFirstOrDefault("O") + "K")
}
@@ -85,7 +85,7 @@
@Test
fun testAwaitFirstOrDefaultWithValues() {
- val flux = GlobalScope.flux {
+ val flux = flux {
send(Flux.just("O", "#").awaitFirstOrDefault("!") + "K")
}
@@ -96,7 +96,7 @@
@Test
fun testAwaitFirstOrNull() {
- val flux = GlobalScope.flux<String> {
+ val flux = flux<String?> {
send(Flux.empty<String>().awaitFirstOrNull() ?: "OK")
}
@@ -107,7 +107,7 @@
@Test
fun testAwaitFirstOrNullWithValues() {
- val flux = GlobalScope.flux {
+ val flux = flux {
send((Flux.just("O", "#").awaitFirstOrNull() ?: "!") + "K")
}
@@ -118,7 +118,7 @@
@Test
fun testAwaitFirstOrElse() {
- val flux = GlobalScope.flux {
+ val flux = flux {
send(Flux.empty<String>().awaitFirstOrElse { "O" } + "K")
}
@@ -129,7 +129,7 @@
@Test
fun testAwaitFirstOrElseWithValues() {
- val flux = GlobalScope.flux {
+ val flux = flux {
send(Flux.just("O", "#").awaitFirstOrElse { "!" } + "K")
}
@@ -140,7 +140,7 @@
@Test
fun testAwaitLast() {
- val flux = GlobalScope.flux {
+ val flux = flux {
send(Flux.just("#", "O").awaitLast() + "K")
}
@@ -151,7 +151,7 @@
@Test
fun testExceptionFromObservable() {
- val flux = GlobalScope.flux {
+ val flux = flux {
try {
send(Flux.error<String>(RuntimeException("O")).awaitFirst())
} catch (e: RuntimeException) {
@@ -166,7 +166,7 @@
@Test
fun testExceptionFromCoroutine() {
- val flux = GlobalScope.flux<String> {
+ val flux = flux<String> {
error(Flux.just("O").awaitSingle() + "K")
}
@@ -178,7 +178,7 @@
@Test
fun testFluxIteration() {
- val flux = GlobalScope.flux {
+ val flux = flux {
var result = ""
Flux.just("O", "K").collect { result += it }
send(result)
@@ -191,7 +191,7 @@
@Test
fun testFluxIterationFailure() {
- val flux = GlobalScope.flux {
+ val flux = flux {
try {
Flux.error<String>(RuntimeException("OK")).collect { fail("Should not be here") }
send("Fail")
diff --git a/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt b/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt
index a0368f8..ee26455 100644
--- a/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/FluxTest.kt
@@ -15,7 +15,7 @@
@Test
fun testBasicSuccess() = runBlocking {
expect(1)
- val flux = flux {
+ val flux = flux(currentDispatcher()) {
expect(4)
send("OK")
}
@@ -32,7 +32,7 @@
@Test
fun testBasicFailure() = runBlocking {
expect(1)
- val flux = flux<String>(NonCancellable) {
+ val flux = flux<String>(currentDispatcher()) {
expect(4)
throw RuntimeException("OK")
}
@@ -52,7 +52,7 @@
@Test
fun testBasicUnsubscribe() = runBlocking {
expect(1)
- val flux = flux<String> {
+ val flux = flux<String>(currentDispatcher()) {
expect(4)
yield() // back to main, will get cancelled
expectUnreached()
@@ -72,23 +72,10 @@
}
@Test
- fun testCancelsParentOnFailure() = runTest(
- expected = { it is RuntimeException && it.message == "OK" }
- ) {
- // has parent, so should cancel it on failure
- flux<Unit> {
- throw RuntimeException("OK")
- }.subscribe(
- { expectUnreached() },
- { assert(it is RuntimeException) }
- )
- }
-
- @Test
fun testNotifyOnceOnCancellation() = runTest {
expect(1)
val observable =
- flux {
+ flux(currentDispatcher()) {
expect(5)
send("OK")
try {
@@ -124,7 +111,7 @@
@Test
fun testFailingConsumer() = runTest {
- val pub = flux {
+ val pub = flux(currentDispatcher()) {
repeat(3) {
expect(it + 1) // expect(1), expect(2) *should* be invoked
send(it)
@@ -138,4 +125,9 @@
finish(3)
}
}
+
+ @Test
+ fun testIllegalArgumentException() {
+ assertFailsWith<IllegalArgumentException> { flux<Int>(Job()) { } }
+ }
}
\ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt b/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt
index 7c72edc..2283d45 100644
--- a/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt
+++ b/reactive/kotlinx-coroutines-reactor/test/MonoTest.kt
@@ -22,14 +22,14 @@
@Test
fun testBasicSuccess() = runBlocking {
expect(1)
- val mono = mono {
+ val mono = mono(currentDispatcher()) {
expect(4)
"OK"
}
expect(2)
mono.subscribe { value ->
expect(5)
- Assert.assertThat(value, IsEqual("OK"))
+ assertThat(value, IsEqual("OK"))
}
expect(3)
yield() // to started coroutine
@@ -39,7 +39,7 @@
@Test
fun testBasicFailure() = runBlocking {
expect(1)
- val mono = mono(NonCancellable) {
+ val mono = mono(currentDispatcher()) {
expect(4)
throw RuntimeException("OK")
}
@@ -48,8 +48,8 @@
expectUnreached()
}, { error ->
expect(5)
- Assert.assertThat(error, IsInstanceOf(RuntimeException::class.java))
- Assert.assertThat(error.message, IsEqual("OK"))
+ assertThat(error, IsInstanceOf(RuntimeException::class.java))
+ assertThat(error.message, IsEqual("OK"))
})
expect(3)
yield() // to started coroutine
@@ -59,7 +59,7 @@
@Test
fun testBasicEmpty() = runBlocking {
expect(1)
- val mono = mono {
+ val mono = mono(currentDispatcher()) {
expect(4)
null
}
@@ -75,7 +75,7 @@
@Test
fun testBasicUnsubscribe() = runBlocking {
expect(1)
- val mono = mono {
+ val mono = mono(currentDispatcher()) {
expect(4)
yield() // back to main, will get cancelled
expectUnreached()
@@ -97,7 +97,7 @@
@Test
fun testMonoNoWait() {
- val mono = GlobalScope.mono {
+ val mono = mono {
"OK"
}
@@ -113,7 +113,7 @@
@Test
fun testMonoEmitAndAwait() {
- val mono = GlobalScope.mono {
+ val mono = mono {
Mono.just("O").awaitSingle() + "K"
}
@@ -124,7 +124,7 @@
@Test
fun testMonoWithDelay() {
- val mono = GlobalScope.mono {
+ val mono = mono {
Flux.just("O").delayElements(ofMillis(50)).awaitSingle() + "K"
}
@@ -135,7 +135,7 @@
@Test
fun testMonoException() {
- val mono = GlobalScope.mono {
+ val mono = mono {
Flux.just("O", "K").awaitSingle() + "K"
}
@@ -146,7 +146,7 @@
@Test
fun testAwaitFirst() {
- val mono = GlobalScope.mono {
+ val mono = mono {
Flux.just("O", "#").awaitFirst() + "K"
}
@@ -157,7 +157,7 @@
@Test
fun testAwaitLast() {
- val mono = GlobalScope.mono {
+ val mono = mono {
Flux.just("#", "O").awaitLast() + "K"
}
@@ -168,7 +168,7 @@
@Test
fun testExceptionFromFlux() {
- val mono = GlobalScope.mono {
+ val mono = mono {
try {
Flux.error<String>(RuntimeException("O")).awaitFirst()
} catch (e: RuntimeException) {
@@ -183,7 +183,7 @@
@Test
fun testExceptionFromCoroutine() {
- val mono = GlobalScope.mono<String> {
+ val mono = mono<String> {
throw IllegalStateException(Flux.just("O").awaitSingle() + "K")
}
@@ -194,21 +194,8 @@
}
@Test
- fun testCancelsParentOnFailure() = runTest(
- expected = { it is RuntimeException && it.message == "OK" }
- ) {
- // has parent, so should cancel it on failure
- mono<Unit> {
- throw RuntimeException("OK")
- }.subscribe(
- { expectUnreached() },
- { assert(it is RuntimeException) }
- )
- }
-
- @Test
fun testSuppressedException() = runTest {
- val mono = mono(NonCancellable) {
+ val mono = mono(currentDispatcher()) {
launch(start = CoroutineStart.ATOMIC) {
throw TestException() // child coroutine fails
}
@@ -227,12 +214,14 @@
}
@Test
- fun testUnhandledException() = runTest(
- unhandled = listOf { it -> it is TestException }
- ) {
+ fun testUnhandledException() = runTest {
expect(1)
var subscription: Subscription? = null
- val mono = mono(NonCancellable) {
+ val mono = mono(currentDispatcher() + CoroutineExceptionHandler { _, t ->
+ assertTrue(t is TestException)
+ expect(5)
+
+ }) {
expect(4)
subscription!!.cancel() // cancel our own subscription, so that delay will get cancelled
try {
@@ -252,6 +241,11 @@
})
expect(3)
yield() // run coroutine
- finish(5)
+ finish(6)
+ }
+
+ @Test
+ fun testIllegalArgumentException() {
+ assertFailsWith<IllegalArgumentException> { mono(Job()) { } }
}
}
diff --git a/reactive/kotlinx-coroutines-reactor/test/ReactorContextTest.kt b/reactive/kotlinx-coroutines-reactor/test/ReactorContextTest.kt
new file mode 100644
index 0000000..1fb4f0b
--- /dev/null
+++ b/reactive/kotlinx-coroutines-reactor/test/ReactorContextTest.kt
@@ -0,0 +1,32 @@
+package kotlinx.coroutines.reactor
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.reactive.*
+import org.junit.Test
+import reactor.util.context.Context
+import kotlin.test.assertEquals
+
+class ReactorContextTest {
+ @Test
+ fun testMonoHookedContext() = runBlocking {
+ val mono = mono(Context.of(1, "1", 7, "7").asCoroutineContext()) {
+ val ctx = coroutineContext[ReactorContext]?.context
+ buildString {
+ (1..7).forEach { append(ctx?.getOrDefault(it, "noValue")) }
+ }
+ } .subscriberContext(Context.of(2, "2", 3, "3", 4, "4", 5, "5"))
+ .subscriberContext { ctx -> ctx.put(6, "6") }
+ assertEquals(mono.awaitFirst(), "1234567")
+ }
+
+ @Test
+ fun testFluxContext() = runBlocking<Unit> {
+ val flux = flux(Context.of(1, "1", 7, "7").asCoroutineContext()) {
+ val ctx = coroutineContext[ReactorContext]!!.context
+ (1..7).forEach { send(ctx.getOrDefault(it, "noValue")) }
+ } .subscriberContext(Context.of(2, "2", 3, "3", 4, "4", 5, "5"))
+ .subscriberContext { ctx -> ctx.put(6, "6") }
+ var i = 0
+ flux.subscribe { str -> i++; assertEquals(str, i.toString()) }
+ }
+}
\ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/README.md b/reactive/kotlinx-coroutines-rx2/README.md
index f1079b6..fbdf1b3 100644
--- a/reactive/kotlinx-coroutines-rx2/README.md
+++ b/reactive/kotlinx-coroutines-rx2/README.md
@@ -51,11 +51,11 @@
[ReceiveChannel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/index.html
<!--- MODULE kotlinx-coroutines-rx2 -->
<!--- INDEX kotlinx.coroutines.rx2 -->
-[rxCompletable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/kotlinx.coroutines.-coroutine-scope/rx-completable.html
-[rxMaybe]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/kotlinx.coroutines.-coroutine-scope/rx-maybe.html
-[rxSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/kotlinx.coroutines.-coroutine-scope/rx-single.html
-[rxObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/kotlinx.coroutines.-coroutine-scope/rx-observable.html
-[rxFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/kotlinx.coroutines.-coroutine-scope/rx-flowable.html
+[rxCompletable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-completable.html
+[rxMaybe]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-maybe.html
+[rxSingle]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-single.html
+[rxObservable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-observable.html
+[rxFlowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/rx-flowable.html
[io.reactivex.CompletableSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-completable-source/await.html
[io.reactivex.MaybeSource.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-maybe-source/await.html
[io.reactivex.MaybeSource.awaitOrDefault]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-maybe-source/await-or-default.html
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt b/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt
index f44ecf7..0da0677 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxCompletable.kt
@@ -1,12 +1,15 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
package kotlinx.coroutines.rx2
import io.reactivex.*
import kotlinx.coroutines.*
import kotlin.coroutines.*
+import kotlin.internal.*
/**
* Creates cold [Completable] that runs a given [block] in a coroutine.
@@ -18,19 +21,36 @@
* | Completes successfully | `onCompleted`
* | Failure with exception or unsubscribe | `onError`
*
- * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * Coroutine context can be specified with [context] argument.
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
- * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
- * with corresponding [coroutineContext] element.
- *
- * @param context context of the coroutine.
- * @param block the coroutine code.
+ * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
*/
+public fun rxCompletable(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> Unit
+): Completable {
+ require(context[Job] === null) { "Completable context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return rxCompletableInternal(GlobalScope, context, block)
+}
+
+@Deprecated(
+ message = "CoroutineScope.rxCompletable is deprecated in favour of top-level rxCompletable",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("rxCompletable(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
+@LowPriorityInOverloadResolution
public fun CoroutineScope.rxCompletable(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> Unit
+): Completable = rxCompletableInternal(this, context, block)
+
+private fun rxCompletableInternal(
+ scope: CoroutineScope, // support for legacy rxCompletable in scope
+ context: CoroutineContext,
+ block: suspend CoroutineScope.() -> Unit
): Completable = Completable.create { subscriber ->
- val newContext = newCoroutineContext(context)
+ val newContext = scope.newCoroutineContext(context)
val coroutine = RxCompletableCoroutine(newContext, subscriber)
subscriber.setCancellable(RxCancellable(coroutine))
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
@@ -41,12 +61,20 @@
private val subscriber: CompletableEmitter
) : AbstractCoroutine<Unit>(parentContext, true) {
override fun onCompleted(value: Unit) {
- if (!subscriber.isDisposed) subscriber.onComplete()
+ try {
+ if (!subscriber.isDisposed) subscriber.onComplete()
+ } catch (e: Throwable) {
+ handleCoroutineException(context, e)
+ }
}
override fun onCancelled(cause: Throwable, handled: Boolean) {
if (!subscriber.isDisposed) {
- subscriber.onError(cause)
+ try {
+ subscriber.onError(cause)
+ } catch (e: Throwable) {
+ handleCoroutineException(context, e)
+ }
} else if (!handled) {
handleCoroutineException(context, cause)
}
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt b/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt
index dbf29f1..d5678de 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt
@@ -25,7 +25,7 @@
* @param context -- the coroutine context from which the resulting completable is going to be signalled
*/
@ExperimentalCoroutinesApi
-public fun Job.asCompletable(context: CoroutineContext): Completable = GlobalScope.rxCompletable(context) {
+public fun Job.asCompletable(context: CoroutineContext): Completable = rxCompletable(context) {
this@asCompletable.join()
}
@@ -42,7 +42,7 @@
* @param context -- the coroutine context from which the resulting maybe is going to be signalled
*/
@ExperimentalCoroutinesApi
-public fun <T> Deferred<T?>.asMaybe(context: CoroutineContext): Maybe<T> = GlobalScope.rxMaybe(context) {
+public fun <T> Deferred<T?>.asMaybe(context: CoroutineContext): Maybe<T> = rxMaybe(context) {
this@asMaybe.await()
}
@@ -59,7 +59,7 @@
* @param context -- the coroutine context from which the resulting single is going to be signalled
*/
@ExperimentalCoroutinesApi
-public fun <T : Any> Deferred<T>.asSingle(context: CoroutineContext): Single<T> = GlobalScope.rxSingle(context) {
+public fun <T : Any> Deferred<T>.asSingle(context: CoroutineContext): Single<T> = rxSingle(context) {
this@asSingle.await()
}
@@ -75,7 +75,7 @@
* @param context -- the coroutine context from which the resulting observable is going to be signalled
*/
@ObsoleteCoroutinesApi
-public fun <T : Any> ReceiveChannel<T>.asObservable(context: CoroutineContext): Observable<T> = GlobalScope.rxObservable(context) {
+public fun <T : Any> ReceiveChannel<T>.asObservable(context: CoroutineContext): Observable<T> = rxObservable(context) {
for (t in this@asObservable)
send(t)
}
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt b/reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt
index 93d6079..beee40e 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxFlowable.kt
@@ -1,7 +1,9 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
package kotlinx.coroutines.rx2
import io.reactivex.*
@@ -9,6 +11,7 @@
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.reactive.*
import kotlin.coroutines.*
+import kotlin.internal.*
/**
* Creates cold [flowable][Flowable] that will run a given [block] in a coroutine.
@@ -24,19 +27,29 @@
* | Normal completion or `close` without cause | `onComplete`
* | Failure with exception or `close` with cause | `onError`
*
- * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * Coroutine context can be specified with [context] argument.
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
- * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
- * with corresponding [coroutineContext] element.
+ * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
*
* **Note: This is an experimental api.** Behaviour of publishers that work as children in a parent scope with respect
- * to cancellation and error handling may change in the future.
- *
- * @param context context of the coroutine.
- * @param block the coroutine code.
*/
@ExperimentalCoroutinesApi
+public fun <T: Any> rxFlowable(
+ context: CoroutineContext = EmptyCoroutineContext,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Flowable<T> {
+ require(context[Job] === null) { "Flowable context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return Flowable.fromPublisher(publishInternal(GlobalScope, context, block))
+}
+
+@Deprecated(
+ message = "CoroutineScope.rxFlowable is deprecated in favour of top-level rxFlowable",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("rxFlowable(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
+@LowPriorityInOverloadResolution
public fun <T: Any> CoroutineScope.rxFlowable(
context: CoroutineContext = EmptyCoroutineContext,
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
-): Flowable<T> = Flowable.fromPublisher(publish(newCoroutineContext(context), block = block))
+): Flowable<T> = Flowable.fromPublisher(publishInternal(this, context, block))
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt b/reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt
index 3e3f13b..fbc366c 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxMaybe.kt
@@ -1,12 +1,15 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
package kotlinx.coroutines.rx2
import io.reactivex.*
import kotlinx.coroutines.*
import kotlin.coroutines.*
+import kotlin.internal.*
/**
* Creates cold [maybe][Maybe] that will run a given [block] in a coroutine.
@@ -19,19 +22,36 @@
* | Returns a null | `onComplete`
* | Failure with exception or unsubscribe | `onError`
*
- * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * Coroutine context can be specified with [context] argument.
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
- * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
- * with corresponding [coroutineContext] element.
- *
- * @param context context of the coroutine.
- * @param block the coroutine code.
+ * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
*/
+public fun <T> rxMaybe(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> T?
+): Maybe<T> {
+ require(context[Job] === null) { "Maybe context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return rxMaybeInternal(GlobalScope, context, block)
+}
+
+@Deprecated(
+ message = "CoroutineScope.rxMaybe is deprecated in favour of top-level rxMaybe",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("rxMaybe(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
+@LowPriorityInOverloadResolution
public fun <T> CoroutineScope.rxMaybe(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T?
+): Maybe<T> = rxMaybeInternal(this, context, block)
+
+private fun <T> rxMaybeInternal(
+ scope: CoroutineScope, // support for legacy rxMaybe in scope
+ context: CoroutineContext,
+ block: suspend CoroutineScope.() -> T?
): Maybe<T> = Maybe.create { subscriber ->
- val newContext = newCoroutineContext(context)
+ val newContext = scope.newCoroutineContext(context)
val coroutine = RxMaybeCoroutine(newContext, subscriber)
subscriber.setCancellable(RxCancellable(coroutine))
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
@@ -43,13 +63,21 @@
) : AbstractCoroutine<T>(parentContext, true) {
override fun onCompleted(value: T) {
if (!subscriber.isDisposed) {
- if (value == null) subscriber.onComplete() else subscriber.onSuccess(value)
+ try {
+ if (value == null) subscriber.onComplete() else subscriber.onSuccess(value)
+ } catch(e: Throwable) {
+ handleCoroutineException(context, e)
+ }
}
}
override fun onCancelled(cause: Throwable, handled: Boolean) {
if (!subscriber.isDisposed) {
- subscriber.onError(cause)
+ try {
+ subscriber.onError(cause)
+ } catch (e: Throwable) {
+ handleCoroutineException(context, e)
+ }
} else if (!handled) {
handleCoroutineException(context, cause)
}
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt
index f78f5ea..3d0ccd8 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt
@@ -1,16 +1,20 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
package kotlinx.coroutines.rx2
import io.reactivex.*
+import io.reactivex.exceptions.*
import kotlinx.atomicfu.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.selects.*
import kotlinx.coroutines.sync.*
import kotlin.coroutines.*
+import kotlin.internal.*
/**
* Creates cold [observable][Observable] that will run a given [block] in a coroutine.
@@ -26,23 +30,37 @@
* | Normal completion or `close` without cause | `onComplete`
* | Failure with exception or `close` with cause | `onError`
*
- * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * Coroutine context can be specified with [context] argument.
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
- * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
- * with corresponding [coroutineContext] element.
- *
- * **Note: This is an experimental api.** Behaviour of publishers that work as children in a parent scope with respect
- * to cancellation and error handling may change in the future.
- *
- * @param context context of the coroutine.
- * @param block the coroutine code.
+ * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
*/
@ExperimentalCoroutinesApi
+public fun <T : Any> rxObservable(
+ context: CoroutineContext = EmptyCoroutineContext,
+ @BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Observable<T> {
+ require(context[Job] === null) { "Observable context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return rxObservableInternal(GlobalScope, context, block)
+}
+
+@Deprecated(
+ message = "CoroutineScope.rxObservable is deprecated in favour of top-level rxObservable",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("rxObservable(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
+@LowPriorityInOverloadResolution
public fun <T : Any> CoroutineScope.rxObservable(
context: CoroutineContext = EmptyCoroutineContext,
@BuilderInference block: suspend ProducerScope<T>.() -> Unit
+): Observable<T> = rxObservableInternal(this, context, block)
+
+private fun <T : Any> rxObservableInternal(
+ scope: CoroutineScope, // support for legacy rxObservable in scope
+ context: CoroutineContext,
+ block: suspend ProducerScope<T>.() -> Unit
): Observable<T> = Observable.create { subscriber ->
- val newContext = newCoroutineContext(context)
+ val newContext = scope.newCoroutineContext(context)
val coroutine = RxObservableCoroutine(newContext, subscriber)
subscriber.setCancellable(RxCancellable(coroutine)) // do it first (before starting coroutine), to await unnecessary suspensions
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
@@ -114,15 +132,19 @@
// to abort the corresponding send/offer invocation. From the standpoint of coroutines machinery,
// this failure is essentially equivalent to a failure of a child coroutine.
cancelCoroutine(e)
- doLockedSignalCompleted(e, false)
+ mutex.unlock()
throw e
}
/*
- There is no sense to check for `isActive` before doing `unlock`, because cancellation/completion might
- happen after this check and before `unlock` (see signalCompleted that does not do anything
- if it fails to acquire the lock that we are still holding).
- We have to recheck `isCompleted` after `unlock` anyway.
+ * There is no sense to check for `isActive` before doing `unlock`, because cancellation/completion might
+ * happen after this check and before `unlock` (see signalCompleted that does not do anything
+ * if it fails to acquire the lock that we are still holding).
+ * We have to recheck `isCompleted` after `unlock` anyway.
*/
+ unlockAndCheckCompleted()
+ }
+
+ private fun unlockAndCheckCompleted() {
mutex.unlock()
// recheck isActive
if (!isActive && mutex.tryLock())
@@ -131,16 +153,31 @@
// assert: mutex.isLocked()
private fun doLockedSignalCompleted(cause: Throwable?, handled: Boolean) {
- // todo: handled is ignored here, might need something like in PublisherCoroutine to process
// cancellation failures
try {
if (_signal.value >= CLOSED) {
_signal.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed)
try {
- if (cause != null && cause !is CancellationException)
+ if (cause != null && cause !is CancellationException) {
+ /*
+ * Reactive frameworks have two types of exceptions: regular and fatal.
+ * Regular are passed to onError.
+ * Fatal can be passed to onError, but even the standard implementations **can just swallow it** (e.g. see #1297).
+ * Such behaviour is inconsistent, leads to silent failures and we can't possibly know whether
+ * the cause will be handled by onError (and moreover, it depends on whether a fatal exception was
+ * thrown by subscriber or upstream).
+ * To make behaviour consistent and least surprising, we always handle fatal exceptions
+ * by coroutines machinery, anyway, they should not be present in regular program flow,
+ * thus our goal here is just to expose it as soon as possible.
+ */
subscriber.onError(cause)
- else
+ if (!handled && cause.isFatal()) {
+ handleCoroutineException(context, cause)
+ }
+ }
+ else {
subscriber.onComplete()
+ }
} catch (e: Throwable) {
// Unhandled exception (cannot handle in other way, since we are already complete)
handleCoroutineException(context, e)
@@ -164,4 +201,11 @@
override fun onCancelled(cause: Throwable, handled: Boolean) {
signalCompleted(cause, handled)
}
+}
+
+internal fun Throwable.isFatal() = try {
+ Exceptions.throwIfFatal(this) // Rx-consistent behaviour without hardcode
+ false
+} catch (e: Throwable) {
+ true
}
\ No newline at end of file
diff --git a/reactive/kotlinx-coroutines-rx2/src/RxSingle.kt b/reactive/kotlinx-coroutines-rx2/src/RxSingle.kt
index 53992d4..b6cebf0 100644
--- a/reactive/kotlinx-coroutines-rx2/src/RxSingle.kt
+++ b/reactive/kotlinx-coroutines-rx2/src/RxSingle.kt
@@ -1,12 +1,15 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
package kotlinx.coroutines.rx2
import io.reactivex.*
import kotlinx.coroutines.*
import kotlin.coroutines.*
+import kotlin.internal.*
/**
* Creates cold [single][Single] that will run a given [block] in a coroutine.
@@ -18,19 +21,36 @@
* | Returns a value | `onSuccess`
* | Failure with exception or unsubscribe | `onError`
*
- * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
+ * Coroutine context can be specified with [context] argument.
* If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
- * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
- * with corresponding [coroutineContext] element.
- *
- * @param context context of the coroutine.
- * @param block the coroutine code.
+ * Method throws [IllegalArgumentException] if provided [context] contains a [Job] instance.
*/
+public fun <T : Any> rxSingle(
+ context: CoroutineContext = EmptyCoroutineContext,
+ block: suspend CoroutineScope.() -> T
+): Single<T> {
+ require(context[Job] === null) { "Single context cannot contain job in it." +
+ "Its lifecycle should be managed via Disposable handle. Had $context" }
+ return rxSingleInternal(GlobalScope, context, block)
+}
+
+@Deprecated(
+ message = "CoroutineScope.rxSingle is deprecated in favour of top-level rxSingle",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("rxSingle(context, block)")
+) // Since 1.3.0, will be error in 1.3.1 and hidden in 1.4.0
+@LowPriorityInOverloadResolution
public fun <T : Any> CoroutineScope.rxSingle(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> T
+): Single<T> = rxSingleInternal(this, context, block)
+
+private fun <T : Any> rxSingleInternal(
+ scope: CoroutineScope, // support for legacy rxSingle in scope
+ context: CoroutineContext,
+ block: suspend CoroutineScope.() -> T
): Single<T> = Single.create { subscriber ->
- val newContext = newCoroutineContext(context)
+ val newContext = scope.newCoroutineContext(context)
val coroutine = RxSingleCoroutine(newContext, subscriber)
subscriber.setCancellable(RxCancellable(coroutine))
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
@@ -41,12 +61,20 @@
private val subscriber: SingleEmitter<T>
) : AbstractCoroutine<T>(parentContext, true) {
override fun onCompleted(value: T) {
- if (!subscriber.isDisposed) subscriber.onSuccess(value)
+ try {
+ if (!subscriber.isDisposed) subscriber.onSuccess(value)
+ } catch (e: Throwable) {
+ handleCoroutineException(context, e)
+ }
}
override fun onCancelled(cause: Throwable, handled: Boolean) {
if (!subscriber.isDisposed) {
- subscriber.onError(cause)
+ try {
+ subscriber.onError(cause)
+ } catch (e: Throwable) {
+ handleCoroutineException(context, e)
+ }
} else if (!handled) {
handleCoroutineException(context, cause)
}
diff --git a/reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt b/reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt
index a11807c..a7caea4 100644
--- a/reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/CompletableTest.kt
@@ -15,7 +15,7 @@
@Test
fun testBasicSuccess() = runBlocking {
expect(1)
- val completable = rxCompletable {
+ val completable = rxCompletable(currentDispatcher()) {
expect(4)
}
expect(2)
@@ -30,7 +30,7 @@
@Test
fun testBasicFailure() = runBlocking {
expect(1)
- val completable = rxCompletable(NonCancellable) {
+ val completable = rxCompletable(currentDispatcher()) {
expect(4)
throw RuntimeException("OK")
}
@@ -50,7 +50,7 @@
@Test
fun testBasicUnsubscribe() = runBlocking {
expect(1)
- val completable = rxCompletable {
+ val completable = rxCompletable(currentDispatcher()) {
expect(4)
yield() // back to main, will get cancelled
expectUnreached()
@@ -73,7 +73,7 @@
@Test
fun testAwaitSuccess() = runBlocking {
expect(1)
- val completable = rxCompletable {
+ val completable = rxCompletable(currentDispatcher()) {
expect(3)
}
expect(2)
@@ -84,7 +84,7 @@
@Test
fun testAwaitFailure() = runBlocking {
expect(1)
- val completable = rxCompletable(NonCancellable) {
+ val completable = rxCompletable(currentDispatcher()) {
expect(3)
throw RuntimeException("OK")
}
@@ -99,21 +99,8 @@
}
@Test
- fun testCancelsParentOnFailure() = runTest(
- expected = { it is RuntimeException && it.message == "OK" }
- ) {
- // has parent, so should cancel it on failure
- rxCompletable {
- throw RuntimeException("OK")
- }.subscribe(
- { expectUnreached() },
- { assert(it is RuntimeException) }
- )
- }
-
- @Test
fun testSuppressedException() = runTest {
- val completable = rxCompletable(NonCancellable) {
+ val completable = rxCompletable(currentDispatcher()) {
launch(start = CoroutineStart.ATOMIC) {
throw TestException() // child coroutine fails
}
@@ -132,12 +119,14 @@
}
@Test
- fun testUnhandledException() = runTest(
- unhandled = listOf { it -> it is TestException }
- ) {
+ fun testUnhandledException() = runTest() {
expect(1)
var disposable: Disposable? = null
- val completable = rxCompletable(NonCancellable) {
+ val eh = CoroutineExceptionHandler { _, t ->
+ assertTrue(t is TestException)
+ expect(5)
+ }
+ val completable = rxCompletable(currentDispatcher() + eh) {
expect(4)
disposable!!.dispose() // cancel our own subscription, so that delay will get cancelled
try {
@@ -156,6 +145,23 @@
})
expect(3)
yield() // run coroutine
- finish(5)
+ finish(6)
+ }
+
+ @Test
+ fun testFatalExceptionInSubscribe() = runTest {
+ GlobalScope.rxCompletable(Dispatchers.Unconfined + CoroutineExceptionHandler{ _, e -> assertTrue(e is LinkageError); expect(2)}) {
+ expect(1)
+ 42
+ }.subscribe({ throw LinkageError() })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalExceptionInSingle() = runTest {
+ GlobalScope.rxCompletable(Dispatchers.Unconfined) {
+ throw LinkageError()
+ }.subscribe({ expectUnreached() }, { expect(1); assertTrue(it is LinkageError) })
+ finish(2)
}
}
diff --git a/reactive/kotlinx-coroutines-rx2/test/ConvertTest.kt b/reactive/kotlinx-coroutines-rx2/test/ConvertTest.kt
index 475ee57..758b632 100644
--- a/reactive/kotlinx-coroutines-rx2/test/ConvertTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/ConvertTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.rx2
@@ -16,7 +16,7 @@
val job = launch {
expect(3)
}
- val completable = job.asCompletable(coroutineContext)
+ val completable = job.asCompletable(coroutineContext.minusKey(Job))
completable.subscribe {
expect(4)
}
@@ -32,7 +32,7 @@
expect(3)
throw RuntimeException("OK")
}
- val completable = job.asCompletable(coroutineContext)
+ val completable = job.asCompletable(coroutineContext.minusKey(Job))
completable.subscribe {
expect(4)
}
@@ -140,10 +140,10 @@
throw TestException("K")
}
val observable = c.asObservable(Dispatchers.Unconfined)
- val single = GlobalScope.rxSingle(Dispatchers.Unconfined) {
+ val single = rxSingle(Dispatchers.Unconfined) {
var result = ""
try {
- observable.consumeEach { result += it }
+ observable.collect { result += it }
} catch(e: Throwable) {
check(e is TestException)
result += e.message
diff --git a/reactive/kotlinx-coroutines-rx2/test/FlowableExceptionHandlingTest.kt b/reactive/kotlinx-coroutines-rx2/test/FlowableExceptionHandlingTest.kt
new file mode 100644
index 0000000..4f3e724
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/FlowableExceptionHandlingTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.rx2
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import kotlin.test.*
+
+class FlowableExceptionHandlingTest : TestBase() {
+
+ @Before
+ fun setup() {
+ ignoreLostThreads("RxComputationThreadPool-", "RxCachedWorkerPoolEvictor-", "RxSchedulerPurge-")
+ }
+
+ private inline fun <reified T : Throwable> ceh(expect: Int) = CoroutineExceptionHandler { _, t ->
+ assertTrue(t is T)
+ expect(expect)
+ }
+
+ private fun cehUnreached() = CoroutineExceptionHandler { _, _ -> expectUnreached() }
+
+ @Test
+ fun testException() = runTest {
+ rxFlowable<Int>(Dispatchers.Unconfined + cehUnreached()) {
+ expect(1)
+ throw TestException()
+ }.subscribe({
+ expectUnreached()
+ }, {
+ expect(2) // Reported to onError
+ })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalException() = runTest {
+ rxFlowable<Int>(Dispatchers.Unconfined + ceh<LinkageError>(3)) {
+ expect(1)
+ throw LinkageError()
+ }.subscribe({
+ expectUnreached()
+ }, {
+ expect(2) // Fatal exception is reported to both onError and CEH
+ })
+ finish(4)
+ }
+
+ @Test
+ fun testExceptionAsynchronous() = runTest {
+ rxFlowable<Int>(Dispatchers.Unconfined + cehUnreached()) {
+ expect(1)
+ throw TestException()
+ }.publish()
+ .refCount()
+ .subscribe({
+ expectUnreached()
+ }, {
+ expect(2) // Reported to onError
+ })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalExceptionAsynchronous() = runTest {
+ rxFlowable<Int>(Dispatchers.Unconfined + ceh<LinkageError>(3)) {
+ expect(1)
+ throw LinkageError()
+ }.publish()
+ .refCount()
+ .subscribe({
+ expectUnreached()
+ }, {
+ expect(2)
+ })
+ finish(4)
+ }
+
+ @Test
+ fun testFatalExceptionFromSubscribe() = runTest {
+ rxFlowable(Dispatchers.Unconfined + ceh<LinkageError>(4)) {
+ expect(1)
+ send(Unit)
+ }.subscribe({
+ expect(2)
+ throw LinkageError()
+ }, { expect(3) }) // Fatal exception is reported to both onError and CEH
+ finish(5)
+ }
+
+ @Test
+ fun testExceptionFromSubscribe() = runTest {
+ rxFlowable(Dispatchers.Unconfined + cehUnreached()) {
+ expect(1)
+ send(Unit)
+ }.subscribe({
+ expect(2)
+ throw TestException()
+ }, { expect(3) }) // not reported to onError because came from the subscribe itself
+ finish(4)
+ }
+
+ @Test
+ fun testAsynchronousExceptionFromSubscribe() = runTest {
+ rxFlowable(Dispatchers.Unconfined + cehUnreached()) {
+ expect(1)
+ send(Unit)
+ }.publish()
+ .refCount()
+ .subscribe({
+ expect(2)
+ throw RuntimeException()
+ }, { expect(3) })
+ finish(4)
+ }
+
+ @Test
+ fun testAsynchronousFatalExceptionFromSubscribe() = runTest {
+ rxFlowable(Dispatchers.Unconfined + ceh<LinkageError>(3)) {
+ expect(1)
+ send(Unit)
+ }.publish()
+ .refCount()
+ .subscribe({
+ expect(2)
+ throw LinkageError()
+ }, { expectUnreached() })
+ finish(4)
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/FlowableTest.kt b/reactive/kotlinx-coroutines-rx2/test/FlowableTest.kt
index 543de09..aebf999 100644
--- a/reactive/kotlinx-coroutines-rx2/test/FlowableTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/FlowableTest.kt
@@ -15,7 +15,7 @@
@Test
fun testBasicSuccess() = runBlocking {
expect(1)
- val observable = rxFlowable {
+ val observable = rxFlowable(currentDispatcher()) {
expect(4)
send("OK")
}
@@ -32,7 +32,7 @@
@Test
fun testBasicFailure() = runBlocking {
expect(1)
- val observable = rxFlowable<String>(NonCancellable) {
+ val observable = rxFlowable<String>(currentDispatcher()) {
expect(4)
throw RuntimeException("OK")
}
@@ -52,7 +52,7 @@
@Test
fun testBasicUnsubscribe() = runBlocking {
expect(1)
- val observable = rxFlowable<String> {
+ val observable = rxFlowable<String>(currentDispatcher()) {
expect(4)
yield() // back to main, will get cancelled
expectUnreached()
@@ -72,23 +72,10 @@
}
@Test
- fun testCancelsParentOnFailure() = runTest(
- expected = { it is RuntimeException && it.message == "OK" }
- ) {
- // has parent, so should cancel it on failure
- rxFlowable<Unit> {
- throw RuntimeException("OK")
- }.subscribe(
- { expectUnreached() },
- { assert(it is RuntimeException) }
- )
- }
-
- @Test
fun testNotifyOnceOnCancellation() = runTest {
expect(1)
val observable =
- rxFlowable {
+ rxFlowable(currentDispatcher()) {
expect(5)
send("OK")
try {
@@ -124,7 +111,7 @@
@Test
fun testFailingConsumer() = runTest {
- val pub = rxFlowable {
+ val pub = rxFlowable(currentDispatcher()) {
repeat(3) {
expect(it + 1) // expect(1), expect(2) *should* be invoked
send(it)
diff --git a/reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt b/reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt
index 9b55e58..ca7c0ca 100644
--- a/reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/IntegrationTest.kt
@@ -20,7 +20,7 @@
) : TestBase() {
enum class Ctx {
- MAIN { override fun invoke(context: CoroutineContext): CoroutineContext = context },
+ MAIN { override fun invoke(context: CoroutineContext): CoroutineContext = context.minusKey(Job) },
DEFAULT { override fun invoke(context: CoroutineContext): CoroutineContext = Dispatchers.Default },
UNCONFINED { override fun invoke(context: CoroutineContext): CoroutineContext = Dispatchers.Unconfined };
@@ -58,7 +58,7 @@
@Test
fun testSingle() = runBlocking {
- val observable = CoroutineScope(ctx(coroutineContext)).rxObservable {
+ val observable = rxObservable(ctx(coroutineContext)) {
if (delay) delay(1)
send("OK")
}
@@ -101,8 +101,7 @@
fun testCancelWithoutValue() = runTest {
val job = launch(Job(), start = CoroutineStart.UNDISPATCHED) {
rxObservable<String> {
- yield()
- expectUnreached()
+ hang { }
}.awaitFirst()
}
diff --git a/reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt b/reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt
index e97b1f0..dcd6663 100644
--- a/reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/MaybeTest.kt
@@ -24,14 +24,14 @@
@Test
fun testBasicSuccess() = runBlocking {
expect(1)
- val maybe = rxMaybe {
+ val maybe = rxMaybe(currentDispatcher()) {
expect(4)
"OK"
}
expect(2)
maybe.subscribe { value ->
expect(5)
- Assert.assertThat(value, IsEqual("OK"))
+ assertThat(value, IsEqual("OK"))
}
expect(3)
yield() // to started coroutine
@@ -41,7 +41,7 @@
@Test
fun testBasicEmpty() = runBlocking {
expect(1)
- val maybe = rxMaybe {
+ val maybe = rxMaybe(currentDispatcher()) {
expect(4)
null
}
@@ -57,7 +57,7 @@
@Test
fun testBasicFailure() = runBlocking {
expect(1)
- val maybe = rxMaybe(NonCancellable) {
+ val maybe = rxMaybe(currentDispatcher()) {
expect(4)
throw RuntimeException("OK")
}
@@ -78,7 +78,7 @@
@Test
fun testBasicUnsubscribe() = runBlocking {
expect(1)
- val maybe = rxMaybe {
+ val maybe = rxMaybe(currentDispatcher()) {
expect(4)
yield() // back to main, will get cancelled
expectUnreached()
@@ -100,7 +100,7 @@
@Test
fun testMaybeNoWait() {
- val maybe = GlobalScope.rxMaybe {
+ val maybe = rxMaybe {
"OK"
}
@@ -121,7 +121,7 @@
@Test
fun testMaybeEmitAndAwait() {
- val maybe = GlobalScope.rxMaybe {
+ val maybe = rxMaybe {
Maybe.just("O").await() + "K"
}
@@ -132,7 +132,7 @@
@Test
fun testMaybeWithDelay() {
- val maybe = GlobalScope.rxMaybe {
+ val maybe = rxMaybe {
Observable.timer(50, TimeUnit.MILLISECONDS).map { "O" }.awaitSingle() + "K"
}
@@ -143,7 +143,7 @@
@Test
fun testMaybeException() {
- val maybe = GlobalScope.rxMaybe {
+ val maybe = rxMaybe {
Observable.just("O", "K").awaitSingle() + "K"
}
@@ -154,7 +154,7 @@
@Test
fun testAwaitFirst() {
- val maybe = GlobalScope.rxMaybe {
+ val maybe = rxMaybe {
Observable.just("O", "#").awaitFirst() + "K"
}
@@ -165,7 +165,7 @@
@Test
fun testAwaitLast() {
- val maybe = GlobalScope.rxMaybe {
+ val maybe = rxMaybe {
Observable.just("#", "O").awaitLast() + "K"
}
@@ -176,7 +176,7 @@
@Test
fun testExceptionFromObservable() {
- val maybe = GlobalScope.rxMaybe {
+ val maybe = rxMaybe {
try {
Observable.error<String>(RuntimeException("O")).awaitFirst()
} catch (e: RuntimeException) {
@@ -191,7 +191,7 @@
@Test
fun testExceptionFromCoroutine() {
- val maybe = GlobalScope.rxMaybe<String> {
+ val maybe = rxMaybe<String> {
throw IllegalStateException(Observable.just("O").awaitSingle() + "K")
}
@@ -202,22 +202,9 @@
}
@Test
- fun testCancelsParentOnFailure() = runTest(
- expected = { it is RuntimeException && it.message == "OK" }
- ) {
- // has parent, so should cancel it on failure
- rxMaybe<Unit> {
- throw RuntimeException("OK")
- }.subscribe(
- { expectUnreached() },
- { assert(it is RuntimeException) }
- )
- }
-
- @Test
fun testCancelledConsumer() = runTest {
expect(1)
- val maybe = rxMaybe<Int> {
+ val maybe = rxMaybe<Int>(currentDispatcher()) {
expect(4)
try {
delay(Long.MAX_VALUE)
@@ -242,7 +229,7 @@
@Test
fun testSuppressedException() = runTest {
- val maybe = rxMaybe(NonCancellable) {
+ val maybe = rxMaybe(currentDispatcher()) {
launch(start = CoroutineStart.ATOMIC) {
throw TestException() // child coroutine fails
}
@@ -261,12 +248,14 @@
}
@Test
- fun testUnhandledException() = runTest(
- unhandled = listOf { it -> it is TestException }
- ) {
+ fun testUnhandledException() = runTest {
expect(1)
var disposable: Disposable? = null
- val maybe = rxMaybe(NonCancellable) {
+ val eh = CoroutineExceptionHandler { _, t ->
+ assertTrue(t is TestException)
+ expect(5)
+ }
+ val maybe = rxMaybe(currentDispatcher() + eh) {
expect(4)
disposable!!.dispose() // cancel our own subscription, so that delay will get cancelled
try {
@@ -286,6 +275,23 @@
})
expect(3)
yield() // run coroutine
- finish(5)
+ finish(6)
+ }
+
+ @Test
+ fun testFatalExceptionInSubscribe() = runTest {
+ GlobalScope.rxMaybe(Dispatchers.Unconfined + CoroutineExceptionHandler{ _, e -> assertTrue(e is LinkageError); expect(2)}) {
+ expect(1)
+ 42
+ }.subscribe({ throw LinkageError() })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalExceptionInSingle() = runTest {
+ GlobalScope.rxMaybe(Dispatchers.Unconfined) {
+ throw LinkageError()
+ }.subscribe({ expectUnreached() }, { expect(1); assertTrue(it is LinkageError) })
+ finish(2)
}
}
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableExceptionHandlingTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableExceptionHandlingTest.kt
new file mode 100644
index 0000000..6d247cf
--- /dev/null
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableExceptionHandlingTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.rx2
+
+import kotlinx.coroutines.*
+import org.junit.*
+import org.junit.Test
+import kotlin.test.*
+
+class ObservableExceptionHandlingTest : TestBase() {
+
+ @Before
+ fun setup() {
+ ignoreLostThreads("RxComputationThreadPool-", "RxCachedWorkerPoolEvictor-", "RxSchedulerPurge-")
+ }
+
+ private inline fun <reified T : Throwable> ceh(expect: Int) = CoroutineExceptionHandler { _, t ->
+ assertTrue(t is T)
+ expect(expect)
+ }
+
+ private fun cehUnreached() = CoroutineExceptionHandler { _, _ -> expectUnreached() }
+
+ @Test
+ fun testException() = runTest {
+ rxObservable<Int>(Dispatchers.Unconfined + cehUnreached()) {
+ expect(1)
+ throw TestException()
+ }.subscribe({
+ expectUnreached()
+ }, {
+ expect(2) // Reported to onError
+ })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalException() = runTest {
+ rxObservable<Int>(Dispatchers.Unconfined + ceh<LinkageError>(3)) {
+ expect(1)
+ throw LinkageError()
+ }.subscribe({
+ expectUnreached()
+ }, {
+ expect(2)
+ })
+ finish(4)
+ }
+
+ @Test
+ fun testExceptionAsynchronous() = runTest {
+ rxObservable<Int>(Dispatchers.Unconfined) {
+ expect(1)
+ throw TestException()
+ }.publish()
+ .refCount()
+ .subscribe({
+ expectUnreached()
+ }, {
+ expect(2) // Reported to onError
+ })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalExceptionAsynchronous() = runTest {
+ rxObservable<Int>(Dispatchers.Unconfined + ceh<LinkageError>(3)) {
+ expect(1)
+ throw LinkageError()
+ }.publish()
+ .refCount()
+ .subscribe({
+ expectUnreached()
+ }, {
+ expect(2) // Fatal exception is not reported in onError
+ })
+ finish(4)
+ }
+
+ @Test
+ fun testFatalExceptionFromSubscribe() = runTest {
+ rxObservable(Dispatchers.Unconfined + ceh<LinkageError>(4)) {
+ expect(1)
+ send(Unit)
+ }.subscribe({
+ expect(2)
+ throw LinkageError()
+ }, { expect(3) }) // Unreached because fatal errors are rethrown
+ finish(5)
+ }
+
+ @Test
+ fun testExceptionFromSubscribe() = runTest {
+ rxObservable(Dispatchers.Unconfined) {
+ expect(1)
+ send(Unit)
+ }.subscribe({
+ expect(2)
+ throw TestException()
+ }, { expect(3) }) // not reported to onError because came from the subscribe itself
+ finish(4)
+ }
+
+ @Test
+ fun testAsynchronousExceptionFromSubscribe() = runTest {
+ rxObservable(Dispatchers.Unconfined) {
+ expect(1)
+ send(Unit)
+ }.publish()
+ .refCount()
+ .subscribe({
+ expect(2)
+ throw RuntimeException()
+ }, { expect(3) })
+ finish(4)
+ }
+
+ @Test
+ fun testAsynchronousFatalExceptionFromSubscribe() = runTest {
+ rxObservable(Dispatchers.Unconfined + ceh<LinkageError>(4)) {
+ expect(1)
+ send(Unit)
+ }.publish()
+ .refCount()
+ .subscribe({
+ expect(2)
+ throw LinkageError()
+ }, { expect(3) })
+ finish(5)
+ }
+}
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableMultiTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableMultiTest.kt
index 9208195..75f79de 100644
--- a/reactive/kotlinx-coroutines-rx2/test/ObservableMultiTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableMultiTest.kt
@@ -76,7 +76,7 @@
send("O")
throw IOException("K")
}
- val single = GlobalScope.rxSingle {
+ val single = rxSingle {
var result = ""
try {
observable.consumeEach { result += it }
diff --git a/reactive/kotlinx-coroutines-rx2/test/ObservableTest.kt b/reactive/kotlinx-coroutines-rx2/test/ObservableTest.kt
index 8dc7120..c71ef56 100644
--- a/reactive/kotlinx-coroutines-rx2/test/ObservableTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/ObservableTest.kt
@@ -14,7 +14,7 @@
@Test
fun testBasicSuccess() = runBlocking {
expect(1)
- val observable = rxObservable {
+ val observable = rxObservable(currentDispatcher()) {
expect(4)
send("OK")
}
@@ -31,7 +31,7 @@
@Test
fun testBasicFailure() = runBlocking {
expect(1)
- val observable = rxObservable<String>(NonCancellable) {
+ val observable = rxObservable<String>(currentDispatcher()) {
expect(4)
throw RuntimeException("OK")
}
@@ -51,7 +51,7 @@
@Test
fun testBasicUnsubscribe() = runBlocking {
expect(1)
- val observable = rxObservable<String> {
+ val observable = rxObservable<String>(currentDispatcher()) {
expect(4)
yield() // back to main, will get cancelled
expectUnreached()
@@ -71,23 +71,10 @@
}
@Test
- fun testCancelsParentOnFailure() = runTest(
- expected = { it is RuntimeException && it.message == "OK" }
- ) {
- // has parent, so should cancel it on failure
- rxObservable<Unit> {
- throw RuntimeException("OK")
- }.subscribe(
- { expectUnreached() },
- { assert(it is RuntimeException) }
- )
- }
-
- @Test
fun testNotifyOnceOnCancellation() = runTest {
expect(1)
val observable =
- rxObservable {
+ rxObservable(currentDispatcher()) {
expect(5)
send("OK")
try {
@@ -124,7 +111,7 @@
@Test
fun testFailingConsumer() = runTest {
expect(1)
- val pub = rxObservable {
+ val pub = rxObservable(currentDispatcher()) {
expect(2)
send("OK")
try {
diff --git a/reactive/kotlinx-coroutines-rx2/test/SingleTest.kt b/reactive/kotlinx-coroutines-rx2/test/SingleTest.kt
index 2ae9570..fce7723 100644
--- a/reactive/kotlinx-coroutines-rx2/test/SingleTest.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/SingleTest.kt
@@ -6,6 +6,7 @@
import io.reactivex.*
import io.reactivex.disposables.*
+import io.reactivex.functions.*
import kotlinx.coroutines.*
import org.hamcrest.core.*
import org.junit.*
@@ -21,14 +22,14 @@
@Test
fun testBasicSuccess() = runBlocking {
expect(1)
- val single = rxSingle {
+ val single = rxSingle(currentDispatcher()) {
expect(4)
"OK"
}
expect(2)
single.subscribe { value ->
expect(5)
- Assert.assertThat(value, IsEqual("OK"))
+ assertThat(value, IsEqual("OK"))
}
expect(3)
yield() // to started coroutine
@@ -38,7 +39,7 @@
@Test
fun testBasicFailure() = runBlocking {
expect(1)
- val single = rxSingle(NonCancellable) {
+ val single = rxSingle(currentDispatcher()) {
expect(4)
throw RuntimeException("OK")
}
@@ -47,8 +48,8 @@
expectUnreached()
}, { error ->
expect(5)
- Assert.assertThat(error, IsInstanceOf(RuntimeException::class.java))
- Assert.assertThat(error.message, IsEqual("OK"))
+ assertThat(error, IsInstanceOf(RuntimeException::class.java))
+ assertThat(error.message, IsEqual("OK"))
})
expect(3)
yield() // to started coroutine
@@ -59,7 +60,7 @@
@Test
fun testBasicUnsubscribe() = runBlocking {
expect(1)
- val single = rxSingle {
+ val single = rxSingle(currentDispatcher()) {
expect(4)
yield() // back to main, will get cancelled
expectUnreached()
@@ -82,7 +83,7 @@
@Test
fun testSingleNoWait() {
- val single = GlobalScope.rxSingle {
+ val single = rxSingle {
"OK"
}
@@ -98,7 +99,7 @@
@Test
fun testSingleEmitAndAwait() {
- val single = GlobalScope.rxSingle {
+ val single = rxSingle {
Single.just("O").await() + "K"
}
@@ -109,7 +110,7 @@
@Test
fun testSingleWithDelay() {
- val single = GlobalScope.rxSingle {
+ val single = rxSingle {
Observable.timer(50, TimeUnit.MILLISECONDS).map { "O" }.awaitSingle() + "K"
}
@@ -120,7 +121,7 @@
@Test
fun testSingleException() {
- val single = GlobalScope.rxSingle {
+ val single = rxSingle {
Observable.just("O", "K").awaitSingle() + "K"
}
@@ -131,7 +132,7 @@
@Test
fun testAwaitFirst() {
- val single = GlobalScope.rxSingle {
+ val single = rxSingle {
Observable.just("O", "#").awaitFirst() + "K"
}
@@ -142,7 +143,7 @@
@Test
fun testAwaitLast() {
- val single = GlobalScope.rxSingle {
+ val single = rxSingle {
Observable.just("#", "O").awaitLast() + "K"
}
@@ -153,7 +154,7 @@
@Test
fun testExceptionFromObservable() {
- val single = GlobalScope.rxSingle {
+ val single = rxSingle {
try {
Observable.error<String>(RuntimeException("O")).awaitFirst()
} catch (e: RuntimeException) {
@@ -168,7 +169,7 @@
@Test
fun testExceptionFromCoroutine() {
- val single = GlobalScope.rxSingle<String> {
+ val single = rxSingle<String> {
throw IllegalStateException(Observable.just("O").awaitSingle() + "K")
}
@@ -179,21 +180,8 @@
}
@Test
- fun testCancelsParentOnFailure() = runTest(
- expected = { it is RuntimeException && it.message == "OK" }
- ) {
- // has parent, so should cancel it on failure
- rxSingle<Unit> {
- throw RuntimeException("OK")
- }.subscribe(
- { expectUnreached() },
- { assert(it is RuntimeException) }
- )
- }
-
- @Test
fun testSuppressedException() = runTest {
- val single = rxSingle(NonCancellable) {
+ val single = rxSingle(currentDispatcher()) {
launch(start = CoroutineStart.ATOMIC) {
throw TestException() // child coroutine fails
}
@@ -212,12 +200,34 @@
}
@Test
- fun testUnhandledException() = runTest(
- unhandled = listOf { it -> it is TestException }
- ) {
+ fun testFatalExceptionInSubscribe() = runTest {
+ GlobalScope.rxSingle(Dispatchers.Unconfined + CoroutineExceptionHandler { _, e -> assertTrue(e is LinkageError); expect(2) }) {
+ expect(1)
+ 42
+ }.subscribe(Consumer {
+ throw LinkageError()
+ })
+ finish(3)
+ }
+
+ @Test
+ fun testFatalExceptionInSingle() = runTest {
+ GlobalScope.rxSingle(Dispatchers.Unconfined) {
+ throw LinkageError()
+ }.subscribe({ _, e -> assertTrue(e is LinkageError); expect(1) })
+
+ finish(2)
+ }
+
+ @Test
+ fun testUnhandledException() = runTest {
expect(1)
var disposable: Disposable? = null
- val single = rxSingle(NonCancellable) {
+ val eh = CoroutineExceptionHandler { _, t ->
+ assertTrue(t is TestException)
+ expect(5)
+ }
+ val single = rxSingle(currentDispatcher() + eh) {
expect(4)
disposable!!.dispose() // cancel our own subscription, so that delay will get cancelled
try {
@@ -236,6 +246,6 @@
})
expect(3)
yield() // run coroutine
- finish(5)
+ finish(6)
}
}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-01.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-01.kt
index 19c67f8..f3bc344 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-01.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-02.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-02.kt
index 6d48cb0..0e0ff2e 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-02.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-03.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-03.kt
index 5846991..b84fc08 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-03.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-04.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-04.kt
index 5c373a7..a08c41f 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-04.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-04.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-05.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-05.kt
index 5fa322e..e6428b9 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-05.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-05.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-06.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-06.kt
index 6c730b3..1f3747f 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-06.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-06.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-07.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-07.kt
index 7d4a788..b4cc9fc 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-07.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-07.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-08.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-08.kt
index dae5066..8e17ac9 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-08.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-08.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-09.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-09.kt
index c43b2a6..738c4ab 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-09.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-basic-09.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-01.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-01.kt
index 2b03fbe..b12e92a 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-01.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-02.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-02.kt
index 9dea738..b87849a 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-02.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
@@ -10,7 +10,7 @@
import kotlinx.coroutines.reactive.*
import kotlin.coroutines.CoroutineContext
-fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = GlobalScope.publish<Int>(context) {
+fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = publish<Int>(context) {
for (x in start until start + count) {
delay(time) // wait before sending each number
send(x)
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-03.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-03.kt
index 35757be..1a214ce 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-03.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
@@ -11,7 +11,7 @@
import io.reactivex.schedulers.Schedulers
import kotlin.coroutines.CoroutineContext
-fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = GlobalScope.publish<Int>(context) {
+fun rangeWithInterval(context: CoroutineContext, time: Long, start: Int, count: Int) = publish<Int>(context) {
for (x in start until start + count) {
delay(time) // wait before sending each number
send(x)
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-04.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-04.kt
index d6d5771..3c5d3fb 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-04.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-04.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-05.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-05.kt
index 614e534..61b54b2 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-05.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-context-05.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-01.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-01.kt
index bf79a65..8268ef2 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-01.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-02.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-02.kt
index defd8e5..5f07ba4 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-02.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
@@ -14,7 +14,7 @@
context: CoroutineContext, // the context to execute this coroutine in
predicate: (T) -> Boolean, // the filter predicate
mapper: (T) -> R // the mapper function
-) = GlobalScope.publish<R>(context) {
+) = publish<R>(context) {
collect { // collect the source stream
if (predicate(it)) // filter part
send(mapper(it)) // map part
@@ -27,6 +27,6 @@
fun main() = runBlocking<Unit> {
range(1, 5)
- .fusedFilterMap(coroutineContext, { it % 2 == 0}, { "$it is even" })
+ .fusedFilterMap(Dispatchers.Unconfined, { it % 2 == 0}, { "$it is even" })
.collect { println(it) } // print all the resulting strings
}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-03.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-03.kt
index f1be007..818a792 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-03.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
@@ -12,7 +12,7 @@
import org.reactivestreams.*
import kotlin.coroutines.*
-fun <T, U> Publisher<T>.takeUntil(context: CoroutineContext, other: Publisher<U>) = GlobalScope.publish<T>(context) {
+fun <T, U> Publisher<T>.takeUntil(context: CoroutineContext, other: Publisher<U>) = publish<T>(context) {
this@takeUntil.openSubscription().consume { // explicitly open channel to Publisher<T>
val current = this
other.openSubscription().consume { // explicitly open channel to Publisher<U>
@@ -35,5 +35,5 @@
fun main() = runBlocking<Unit> {
val slowNums = rangeWithInterval(200, 1, 10) // numbers with 200ms interval
val stop = rangeWithInterval(500, 1, 10) // the first one after 500ms
- slowNums.takeUntil(coroutineContext, stop).collect { println(it) } // let's test it
+ slowNums.takeUntil(Dispatchers.Unconfined, stop).collect { println(it) } // let's test it
}
diff --git a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-04.kt b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-04.kt
index bbc2e7b..12d9c1f 100644
--- a/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-04.kt
+++ b/reactive/kotlinx-coroutines-rx2/test/guide/example-reactive-operators-04.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-reactive.md by Knit tool. Do not edit.
@@ -10,7 +10,7 @@
import org.reactivestreams.*
import kotlin.coroutines.*
-fun <T> Publisher<Publisher<T>>.merge(context: CoroutineContext) = GlobalScope.publish<T>(context) {
+fun <T> Publisher<Publisher<T>>.merge(context: CoroutineContext) = publish<T>(context) {
collect { pub -> // for each publisher collected
launch { // launch a child coroutine
pub.collect { send(it) } // resend all element from this publisher
@@ -33,5 +33,5 @@
}
fun main() = runBlocking<Unit> {
- testPub().merge(coroutineContext).collect { println(it) } // print the whole stream
+ testPub().merge(Dispatchers.Unconfined).collect { println(it) } // print the whole stream
}
diff --git a/settings.gradle b/settings.gradle
index 46d3d98..aa5c68f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -25,6 +25,8 @@
module('kotlinx-coroutines-test')
module('kotlinx-coroutines-debug')
module('stdlib-stubs')
+module('kotlinx-coroutines-bom')
+
module('integration/kotlinx-coroutines-guava')
module('integration/kotlinx-coroutines-jdk8')
diff --git a/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md
index d6e848b..4d12d95 100644
--- a/ui/coroutines-guide-ui.md
+++ b/ui/coroutines-guide-ui.md
@@ -1,6 +1,6 @@
<!--- INCLUDE .*/example-ui-([a-z]+)-([0-9]+)\.kt
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
@@ -165,7 +165,7 @@
`app/build.gradle` file:
```groovy
-implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-M2"
+implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-RC"
```
You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/build.gradle b/ui/kotlinx-coroutines-android/animation-app/app/build.gradle
index 25106ff..b5919be 100644
--- a/ui/kotlinx-coroutines-android/animation-app/app/build.gradle
+++ b/ui/kotlinx-coroutines-android/animation-app/app/build.gradle
@@ -4,7 +4,6 @@
android {
compileSdkVersion 27
- buildToolsVersion '27.0.3'
defaultConfig {
applicationId "org.jetbrains.kotlinx.animation"
minSdkVersion 14
diff --git a/ui/kotlinx-coroutines-android/animation-app/build.gradle b/ui/kotlinx-coroutines-android/animation-app/build.gradle
index 9181f02..f512a87 100644
--- a/ui/kotlinx-coroutines-android/animation-app/build.gradle
+++ b/ui/kotlinx-coroutines-android/animation-app/build.gradle
@@ -6,7 +6,7 @@
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.3.0-alpha04'
+ classpath 'com.android.tools.build:gradle:3.4.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/ui/kotlinx-coroutines-android/animation-app/gradle.properties b/ui/kotlinx-coroutines-android/animation-app/gradle.properties
index ed89e47..1fe12d6 100644
--- a/ui/kotlinx-coroutines-android/animation-app/gradle.properties
+++ b/ui/kotlinx-coroutines-android/animation-app/gradle.properties
@@ -18,6 +18,6 @@
kotlin.coroutines=enable
-kotlin_version=1.3.40
-coroutines_version=1.3.0-M2
+kotlin_version=1.3.41
+coroutines_version=1.3.0-RC
diff --git a/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties b/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties
index f44ff59..caf54fa 100644
--- a/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties
+++ b/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Sat Aug 25 19:20:16 MSK 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
diff --git a/ui/kotlinx-coroutines-android/example-app/app/build.gradle b/ui/kotlinx-coroutines-android/example-app/app/build.gradle
index 33e1337..98257d3 100644
--- a/ui/kotlinx-coroutines-android/example-app/app/build.gradle
+++ b/ui/kotlinx-coroutines-android/example-app/app/build.gradle
@@ -4,7 +4,6 @@
android {
compileSdkVersion 27
- buildToolsVersion '27.0.3'
defaultConfig {
applicationId "com.example.app"
minSdkVersion 14
diff --git a/ui/kotlinx-coroutines-android/example-app/build.gradle b/ui/kotlinx-coroutines-android/example-app/build.gradle
index 9181f02..f512a87 100644
--- a/ui/kotlinx-coroutines-android/example-app/build.gradle
+++ b/ui/kotlinx-coroutines-android/example-app/build.gradle
@@ -6,7 +6,7 @@
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.3.0-alpha04'
+ classpath 'com.android.tools.build:gradle:3.4.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/ui/kotlinx-coroutines-android/example-app/gradle.properties b/ui/kotlinx-coroutines-android/example-app/gradle.properties
index ed89e47..1fe12d6 100644
--- a/ui/kotlinx-coroutines-android/example-app/gradle.properties
+++ b/ui/kotlinx-coroutines-android/example-app/gradle.properties
@@ -18,6 +18,6 @@
kotlin.coroutines=enable
-kotlin_version=1.3.40
-coroutines_version=1.3.0-M2
+kotlin_version=1.3.41
+coroutines_version=1.3.0-RC
diff --git a/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties b/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties
index f44ff59..caf54fa 100644
--- a/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties
+++ b/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Sat Aug 25 19:20:16 MSK 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-01.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-01.kt
index 021d662..58da16d 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-01.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt
index 41baa2e..a7be2f5 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt
index 24c48cd..c2926af 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-actor-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-01.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-01.kt
index 2a1634d..2965c04 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-01.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-02.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-02.kt
index bb7749e..fa27d18 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-02.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-advanced-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-01.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-01.kt
index 35adb40..d7ea599 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-01.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-02.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-02.kt
index ab432ea..45967e7 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-02.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-03.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-03.kt
index a05da83..0610660 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-03.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-basic-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt
index 71b88d9..2ff5a2f 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-01.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt
index 0b7a3b2..6a87025 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-02.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.
diff --git a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt
index 4215e65..1388e63 100644
--- a/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt
+++ b/ui/kotlinx-coroutines-javafx/test/guide/example-ui-blocking-03.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
// This file was automatically generated from coroutines-guide-ui.md by Knit tool. Do not edit.