diff --git a/CHANGES.md b/CHANGES.md index 43045c35c7..3205844b74 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,33 @@ # Change log for kotlinx.coroutines +## Version 1.2.0-alpha + +* Major debug agent improvements. Real stacktraces are merged with coroutine stacktraces for running coroutines, merging heuristic is improved, API is cleaned up and is on its road to stabilization (#997). +* `CoroutineTimeout` rule or JUnit4 is introduced to simplify coroutines debugging (#938). +* Stacktrace recovery improvements. Exceptions with custom properties are no longer copied, `CopyableThrowable` interface is introduced, machinery is [documented](https://github.com/Kotlin/kotlinx.coroutines/blob/develop/docs/debugging.md) (#921, #950). +* `Dispatchers.Unconfined`, `MainCoroutineDispatcher.immediate`, `MainScope` and `CoroutineScope.cancel` are promoted to stable API (#972). +* `CompletableJob` is introduced (#971). +* Structured concurrency is integrated into futures and listenable futures (#1008). +* `ensurePresent` and `isPresent` extensions for `ThreadLocal` (#1028). +* `ensureActive` extensions for `CoroutineContext`, `CoroutineScope` and `Job` (#963). +* `SendChannel.isFull` and `ReceiveChannel.isEmpty` are deprecated (#1053). +* `withContext` checks cancellation on entering (#962). +* Operator `invoke` on `CoroutineDispatcher` (#428). +* Java 8 extensions for `delay` and `withTimeout` now properly handle too large values (#428). +* Performance of `Dispatcher.Main` initialization is significantly improved (#878). +* A global exception handler for fatal exceptions in coroutines is introduced (#808, #773). +* Major improvements in cancellation machinery and exceptions delivery consistency. Cancel with custom exception is completely removed. +* Kotlin version is updated to 1.3.21. +* Do not use private API on newer Androids to handle exceptions (#822). + +Bug fixes: +* Proper `select` support in debug agent (#931). +* Proper `supervisorScope` support in debug agent (#915). +* Throwing `initCause` does no longer trigger an internal error (#933). +* Lazy actors are started when calling `close` in order to cleanup their resources (#939). +* Minor bugs in reactive integrations are fixed (#1008). +* Experimental scheduler shutdown sequence is fixed (#990). + ## Version 1.1.1 * Maintenance release, no changes in the codebase @@ -184,7 +212,7 @@ Visible consequences of include more robust exception handling for large corouti * Introduced IO dispatcher to offload blocking I/O-intensive tasks (see #79). * Introduced `ExecutorCoroutineDispatcher` instead of `CloseableCoroutineDispatcher` (see #385). * Built with Kotlin 1.2.61 and Kotlin/Native 0.8.2. -* JAR files for `kotlinx-coroutines` are now [JEP 238](http://openjdk.java.net/jeps/238) multi-release JAR files. +* JAR files for `kotlinx-coroutines` are now [JEP 238](https://openjdk.java.net/jeps/238) multi-release JAR files. * On JDK9+ `VarHandle` is used for atomic operations instead of `Atomic*FieldUpdater` for better performance. * See [AtomicFu](https://github.com/Kotlin/kotlinx.atomicfu/blob/master/README.md) project for details. * Reversed addition of `BlockingChecker` extension point to control where `runBlocking` can be used (see #227). @@ -221,7 +249,7 @@ Visible consequences of include more robust exception handling for large corouti * Includes multiple fixes to documentation contributed by @paolop, @SahilLone, @rocketraman, @bdavisx, @mtopolnik, @Groostav. * Experimental coroutines scheduler preview (JVM only): * Written from scratch and optimized for communicating coroutines. - * Performs significantly better than ForkJoinPool on coroutine benchmarks and for connected applications with [ktor](http://ktor.io). + * Performs significantly better than ForkJoinPool on coroutine benchmarks and for connected applications with [ktor](https://ktor.io). * Supports automatic creating of new threads for blocking operations running on the same thread pool (with an eye on solving #79), but there is no stable public API for it just yet. * For preview, run JVM with `-Dkotlinx.coroutines.scheduler` option. In this case `DefaultDispatcher` is set to new experimental scheduler instead of FJP-based `CommonPool`. * Submit your feedback to issue #261. @@ -514,7 +542,7 @@ Visible consequences of include more robust exception handling for large corouti * Fixed bug in internal class LockFreeLinkedList that resulted in ISE under stress in extremely rare circumstances. * Integrations: * [quasar](integration/kotlinx-coroutines-quasar): Introduced integration with suspendable JVM functions - that are instrumented with [Parallel Universe Quasar](http://docs.paralleluniverse.co/quasar/) + that are instrumented with [Parallel Universe Quasar](https://docs.paralleluniverse.co/quasar/) (thanks to the help of @pron). * [reactor](reactive/kotlinx-coroutines-reactor): Replaced deprecated `setCancellation` with `onDipose` and updated to Aluminium-SR3 release (courtesy of @yxf07, see #96) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md deleted file mode 100644 index b7bcf0c4cf..0000000000 --- a/COMPATIBILITY.md +++ /dev/null @@ -1,25 +0,0 @@ -# Compatibility policy for kotlinx.coroutines - -All `kotlinx.coroutines` API comes into five flavors: stable, experimental, obsolete, internal and deprecated. - * **Deprecated** API is marked with `@Deprecated` and will be removed in `1.0.0` release. - * **Internal** API is marked with `@InternalCoroutinesApi`. It is intended to be used only by `kotlinx.coroutines` machinery and can (will) be broken without a warning. If you are using internal API, please tell us what problem you are trying to solve, so we can provide a stable alternative. - * **Experimental** API is marked with `ExperimentalCoroutinesApi`. Such API may have (known) design issues or we are unsure about its semantics. - Roughly speaking, there is a chance that those declarations will be deprecated in the near future or the semantics of their behavior may change in the way that may break some code. In that case, proper migration aid - will be provided for next several releases alongside with a stable alternative. - * **Obsolete** API is marked with `@ObsoleteCoroutinesApi`. This API is known to have some serious issues, so it will be replaced with a better alternative. - In the sense of migration and deprecation, it is equal to experimental. - * **Stable** API is public API without any annotations. This API is proven to be stable and it is not going to change. If at some point it will be discovered that such API has unfixable design flaws, - it will be gradually deprecated with proper replacement and migration aid, but won't be removed for at least a year. - -## Migration to 1.0.0 version with Kotlin 1.3 - -The main difference between Kotlin 1.2 and 1.3 is that coroutines are now -stable public API, and thus `kotlinx.coroutines` is leaving its "experimental" status. For that reason, future releases of `kotlinx.coroutines` will be available only for Kotlin 1.3. -Version `1.0.0` (starting with its release candidate build) will have all its deprecated declarations removed and `kotlinx.coroutines.experimental` package will be renamed to `kotlinx.coroutines` without functional changes. -In order to migrate `kotlinx.coroutines` to `1.0.0`, follow these steps: - -1. Update `kotlinx.coroutines` to `0.30.2` version. -2. Inspect compiler warnings about deprecated API and migrate it to a proposed alternative. Most of deprecated API has a corresponding replacement which can be applied from IDEA with quickfix. -3. Update Kotlin version to `1.3.0` and `kotlinx.coroutines` to version `0.30.2-eap13`. Then just get rid of `experimental` suffix in all imports. -4. Update `kotlinx.coroutines` to version `1.0.0` or to the corresponding release candidate of it). - \ No newline at end of file diff --git a/README.md b/README.md index 445481a4ef..e454e09340 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,11 @@ # kotlinx.coroutines -[![official JetBrains project](http://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)](http://www.apache.org/licenses/LICENSE-2.0) -[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=1.1.1) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.1.1) +[![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.2.0-alpha) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.2.0-alpha) Library support for Kotlin coroutines with [multiplatform](#multiplatform) support. -This is a companion version for Kotlin `1.3.20` release. - -**NOTE**: `0.30.2` was the last release with Kotlin 1.2 and experimental coroutines. -See [COMPATIBILITY.md](COMPATIBILITY.md) for details of migration onto the stable Kotlin 1.3 coroutines. +This is a companion version for Kotlin `1.3.21` release. ```kotlin GlobalScope.launch { @@ -19,7 +16,7 @@ GlobalScope.launch { ## Modules -* [common](common/README.md) — common coroutines across all platforms: +* [core](kotlinx-coroutines-core/README.md) — common coroutines across all platforms: * `launch` and `async` coroutine builders; * `Job` and `Deferred` light-weight future with cancellation support; * `MainScope` for Android and UI applications. @@ -29,15 +26,17 @@ GlobalScope.launch { * `coroutineScope` and `supervisorScope` scope builders; * `SupervisorJob` and `CoroutineExceptionHandler` for supervision of coroutines hierarchies; * `select` expression support and more. -* [core](core/README.md) — Kotlin/JVM implementation of common coroutines with additional features: +* [core/jvm](kotlinx-coroutines-core/jvm/) — additional core features available on Kotlin/JVM: * `Dispatchers.IO` dispatcher for blocking coroutines; * `Executor.asCoroutineDispatcher()` extension, custom thread pools, and more. -* [test](core/README.md) — test utilities for coroutines +* [core/js](kotlinx-coroutines-core/js/) — additional core features available on Kotlin/JS: + * Integration with `Promise`; + * Integration with `Window`. +* [test](kotlinx-coroutines-test/README.md) — test utilities for coroutines * `Dispatchers.setMain` to override `Dispatchers.Main` in tests. -* [debug](core/README.md) — debug utilities for coroutines. +* [debug](kotlinx-coroutines-debug/README.md) — debug utilities for coroutines. * `DebugProbes` API to probe, keep track of, print and dump active coroutines. -* [js](js/README.md) — Kotlin/JS implementation of common coroutines with `Promise` support. -* [native](native/README.md) — Kotlin/Native implementation of common coroutines with `runBlocking` single-threaded event loop. + * `CoroutinesTimeout` test rule to automatically dump coroutines on test timeout. * [reactive](reactive/README.md) — modules that provide builders and iteration support for various reactive streams libraries: * Reactive Streams, RxJava 2.x, and Project Reactor. * [ui](ui/README.md) — modules that provide coroutine dispatchers for various single-threaded UI libraries: @@ -56,9 +55,11 @@ GlobalScope.launch { * [Guide to kotlinx.coroutines by example](docs/coroutines-guide.md) (**read it first**) * [Guide to UI programming with coroutines](ui/coroutines-guide-ui.md) * [Guide to reactive streams with coroutines](reactive/coroutines-guide-reactive.md) + * [Debugging capabilities in kotlinx.coroutines](docs/debugging.md) +* [Compatibility policy and experimental annotations](docs/compatibility.md) * [Change log for kotlinx.coroutines](CHANGES.md) * [Coroutines design document (KEEP)](https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md) -* [Full kotlinx.coroutines API reference](http://kotlin.github.io/kotlinx.coroutines) +* [Full kotlinx.coroutines API reference](https://kotlin.github.io/kotlinx.coroutines) ## Using in your projects @@ -74,7 +75,7 @@ Add dependencies (you can also add other modules that you need): org.jetbrains.kotlinx kotlinx-coroutines-core - 1.1.1 + 1.2.0-alpha ``` @@ -82,7 +83,7 @@ And make sure that you use the latest Kotlin version: ```xml - 1.3.20 + 1.3.21 ``` @@ -92,7 +93,7 @@ Add dependencies (you can also add other modules that you need): ```groovy dependencies { - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.0-alpha' } ``` @@ -100,7 +101,7 @@ And make sure that you use the latest Kotlin version: ```groovy buildscript { - ext.kotlin_version = '1.3.20' + ext.kotlin_version = '1.3.21' } ``` @@ -118,7 +119,7 @@ Add dependencies (you can also add other modules that you need): ```groovy dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.0-alpha") } ``` @@ -126,7 +127,7 @@ And make sure that you use the latest Kotlin version: ```groovy plugins { - kotlin("jvm") version "1.3.20" + kotlin("jvm") version "1.3.21" } ``` @@ -146,7 +147,7 @@ Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android) module as dependency when using `kotlinx.coroutines` on Android: ```groovy -implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1' +implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.0-alpha' ``` 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) coroutine dispatcher and also makes sure that in case of crashed coroutine with unhandled exception this diff --git a/RELEASE.md b/RELEASE.md index 3ab6c94de3..3ef2f7cff8 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -49,7 +49,7 @@ To release new `` of `kotlinx-coroutines`: * Wait until "Build" configuration for committed `master` branch passes tests. * Run "Deploy (Configure, RUN THIS ONE)" configuration with the corresponding new version. -2. In [GitHub](http://github.com/kotlin/kotlinx.coroutines) interface: +2. In [GitHub](https://github.com/kotlin/kotlinx.coroutines) interface: * Create new release named as ``. * Cut & paste lines from [`CHANGES.md`](CHANGES.md) into description. @@ -61,7 +61,7 @@ To release new `` of `kotlinx-coroutines`: * Wait until newly published version becomes the most recent. * Sync to Maven Central. -5. Announce new release in [Slack](http://kotlinlang.slack.com) +5. Announce new release in [Slack](https://kotlinlang.slack.com) 6. Switch into `develop` branch:
`git checkout develop` diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 055a393877..728804ad4b 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -7,11 +7,20 @@ apply plugin: "com.github.johnrengelman.shadow" apply plugin: "me.champeau.gradle.jmh" repositories { - maven { url "http://repo.typesafe.com/typesafe/releases/" } + maven { url "https://repo.typesafe.com/typesafe/releases/" } } jmh.jmhVersion = '1.21' +// It is better to use the following to run benchmarks, otherwise you may get unexpected errors: +// ../gradlew --no-daemon cleanJmhJar jmh +jmh { + duplicateClassesStrategy DuplicatesStrategy.INCLUDE + failOnError = true + resultFormat = 'CSV' +// include = ['.*ChannelProducerConsumer.*'] +} + jmhJar { baseName 'benchmarks' classifier = null @@ -20,8 +29,8 @@ jmhJar { } dependencies { + compile "org.openjdk.jmh:jmh-core:1.21" compile 'com.typesafe.akka:akka-actor_2.12:2.5.0' - compile project(':kotlinx-coroutines-core-common') compile project(':kotlinx-coroutines-core') - compile "org.openjdk.jmh:jmh-core:1.21" } + diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt new file mode 100644 index 0000000000..77b907f6e9 --- /dev/null +++ b/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt @@ -0,0 +1,146 @@ +package benchmarks + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.selects.select +import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.infra.Blackhole +import java.lang.Integer.max +import java.util.concurrent.ForkJoinPool +import java.util.concurrent.Phaser +import java.util.concurrent.ThreadLocalRandom +import java.util.concurrent.TimeUnit + + +/** + * Benchmark to measure channel algorithm performance in terms of average time per `send-receive` pair; + * actually, it measures the time for a batch of such operations separated into the specified number of consumers/producers. + * It uses different channels (rendezvous, buffered, unlimited; see [ChannelCreator]) and different dispatchers + * (see [DispatcherCreator]). If the [_3_withSelect] property is set, it invokes `send` and + * `receive` via [select], waiting on a local dummy channel simultaneously, simulating a "cancellation" channel. + * + * Please, be patient, this benchmark takes quite a lot of time to complete. + */ +@Warmup(iterations = 3, time = 500, timeUnit = TimeUnit.MICROSECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MICROSECONDS) +@Fork(value = 3) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +open class ChannelProducerConsumerBenchmark { + @Param + private var _0_dispatcher: DispatcherCreator = DispatcherCreator.FORK_JOIN + + @Param + private var _1_channel: ChannelCreator = ChannelCreator.RENDEZVOUS + + @Param("0", "1000") + private var _2_coroutines: Int = 0 + + @Param("false", "true") + private var _3_withSelect: Boolean = false + + @Param("1", "2", "4") // local machine +// @Param("1", "2", "4", "8", "12") // local machine +// @Param("1", "2", "4", "8", "16", "32", "64", "128", "144") // dasquad +// @Param("1", "2", "4", "8", "16", "32", "64", "96") // Google Cloud + private var _4_parallelism: Int = 0 + + private lateinit var dispatcher: CoroutineDispatcher + private lateinit var channel: Channel + + @InternalCoroutinesApi + @Setup + fun setup() { + dispatcher = _0_dispatcher.create(_4_parallelism) + channel = _1_channel.create() + } + + @Benchmark + fun spmc() { + if (_2_coroutines != 0) return + val producers = max(1, _4_parallelism - 1) + val consumers = 1 + run(producers, consumers) + } + + @Benchmark + fun mpmc() { + val producers = if (_2_coroutines == 0) (_4_parallelism + 1) / 2 else _2_coroutines / 2 + val consumers = producers + run(producers, consumers) + } + + private fun run(producers: Int, consumers: Int) { + val n = APPROX_BATCH_SIZE / producers * producers + val phaser = Phaser(producers + consumers + 1) + // Run producers + repeat(producers) { + GlobalScope.launch(dispatcher) { + val dummy = if (_3_withSelect) _1_channel.create() else null + repeat(n / producers) { + produce(it, dummy) + } + phaser.arrive() + } + } + // Run consumers + repeat(consumers) { + GlobalScope.launch(dispatcher) { + val dummy = if (_3_withSelect) _1_channel.create() else null + repeat(n / consumers) { + consume(dummy) + } + phaser.arrive() + } + } + // Wait until work is done + phaser.arriveAndAwaitAdvance() + } + + private suspend fun produce(element: Int, dummy: Channel?) { + if (_3_withSelect) { + select { + channel.onSend(element) {} + dummy!!.onReceive {} + } + } else { + channel.send(element) + } + doWork() + } + + private suspend fun consume(dummy: Channel?) { + if (_3_withSelect) { + select { + channel.onReceive {} + dummy!!.onReceive {} + } + } else { + channel.receive() + } + doWork() + } +} + +enum class DispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) { + FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }) +} + +enum class ChannelCreator(private val capacity: Int) { + RENDEZVOUS(Channel.RENDEZVOUS), +// BUFFERED_1(1), + BUFFERED_2(2), +// BUFFERED_4(4), + BUFFERED_32(32), + BUFFERED_128(128), + BUFFERED_UNLIMITED(Channel.UNLIMITED); + + fun create(): Channel = Channel(capacity) +} + +private fun doWork(): Unit = Blackhole.consumeCPU(ThreadLocalRandom.current().nextLong(WORK_MIN, WORK_MAX)) + +private const val WORK_MIN = 50L +private const val WORK_MAX = 100L +private const val APPROX_BATCH_SIZE = 100000 \ No newline at end of file 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 fc80f3593d..54aa9cfc68 100644 --- a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt +++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt @@ -5,9 +5,9 @@ public abstract class kotlinx/coroutines/AbstractCoroutine : kotlinx/coroutines/ public final fun getContext ()Lkotlin/coroutines/CoroutineContext; public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public fun isActive ()Z - protected fun onCancellation (Ljava/lang/Throwable;)V + protected fun onCancelled (Ljava/lang/Throwable;Z)V protected fun onCompleted (Ljava/lang/Object;)V - protected fun onCompletedExceptionally (Ljava/lang/Throwable;)V + protected final fun onCompletionInternal (Ljava/lang/Object;)V protected fun onStart ()V public final fun resumeWith (Ljava/lang/Object;)V public final fun start (Lkotlinx/coroutines/CoroutineStart;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V @@ -24,6 +24,7 @@ public final class kotlinx/coroutines/AwaitKt { public final class kotlinx/coroutines/BuildersKt { public static final fun async (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Deferred; public static synthetic fun async$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Deferred; + public static final fun invoke (Lkotlinx/coroutines/CoroutineDispatcher;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun launch (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; public static synthetic fun launch$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job; public static final fun runBlocking (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; @@ -89,7 +90,7 @@ public abstract interface class kotlinx/coroutines/ChildJob : kotlinx/coroutines } public final class kotlinx/coroutines/ChildJob$DefaultImpls { - public static synthetic fun cancel (Lkotlinx/coroutines/ChildJob;)Z + public static synthetic fun cancel (Lkotlinx/coroutines/ChildJob;)V public static fun fold (Lkotlinx/coroutines/ChildJob;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; public static fun get (Lkotlinx/coroutines/ChildJob;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; public static fun minusKey (Lkotlinx/coroutines/ChildJob;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; @@ -103,7 +104,7 @@ public abstract interface class kotlinx/coroutines/CompletableDeferred : kotlinx } public final class kotlinx/coroutines/CompletableDeferred$DefaultImpls { - public static synthetic fun cancel (Lkotlinx/coroutines/CompletableDeferred;)Z + public static synthetic fun cancel (Lkotlinx/coroutines/CompletableDeferred;)V public static fun fold (Lkotlinx/coroutines/CompletableDeferred;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; public static fun get (Lkotlinx/coroutines/CompletableDeferred;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; public static fun minusKey (Lkotlinx/coroutines/CompletableDeferred;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; @@ -117,10 +118,28 @@ public final class kotlinx/coroutines/CompletableDeferredKt { public static synthetic fun CompletableDeferred$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/CompletableDeferred; } +public abstract interface class kotlinx/coroutines/CompletableJob : kotlinx/coroutines/Job { + public abstract fun complete ()Z + public abstract fun completeExceptionally (Ljava/lang/Throwable;)Z +} + +public final class kotlinx/coroutines/CompletableJob$DefaultImpls { + public static synthetic fun cancel (Lkotlinx/coroutines/CompletableJob;)V + public static fun fold (Lkotlinx/coroutines/CompletableJob;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; + public static fun get (Lkotlinx/coroutines/CompletableJob;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; + public static fun minusKey (Lkotlinx/coroutines/CompletableJob;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; + public static fun plus (Lkotlinx/coroutines/CompletableJob;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; + public static fun plus (Lkotlinx/coroutines/CompletableJob;Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job; +} + public final class kotlinx/coroutines/CompletionHandlerException : java/lang/RuntimeException { public fun (Ljava/lang/String;Ljava/lang/Throwable;)V } +public abstract interface class kotlinx/coroutines/CopyableThrowable { + public abstract fun createCopy ()Ljava/lang/Throwable; +} + public final class kotlinx/coroutines/CoroutineContextKt { public static final fun newCoroutineContext (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; } @@ -155,9 +174,7 @@ public final class kotlinx/coroutines/CoroutineExceptionHandler$Key : kotlin/cor public final class kotlinx/coroutines/CoroutineExceptionHandlerKt { public static final fun CoroutineExceptionHandler (Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/CoroutineExceptionHandler; - public static final fun handleCoroutineException (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;Lkotlinx/coroutines/Job;)V - public static synthetic fun handleCoroutineException$default (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;Lkotlinx/coroutines/Job;ILjava/lang/Object;)V - public static final fun handleExceptionViaHandler (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V + public static final fun handleCoroutineException (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V } public final class kotlinx/coroutines/CoroutineName : kotlin/coroutines/AbstractCoroutineContextElement { @@ -182,8 +199,10 @@ public abstract interface class kotlinx/coroutines/CoroutineScope { 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;)V + public static final fun cancel (Lkotlinx/coroutines/CoroutineScope;Ljava/util/concurrent/CancellationException;)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 public static final fun isActive (Lkotlinx/coroutines/CoroutineScope;)Z public static final fun plus (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/CoroutineScope; } @@ -215,7 +234,7 @@ public abstract interface class kotlinx/coroutines/Deferred : kotlinx/coroutines } public final class kotlinx/coroutines/Deferred$DefaultImpls { - public static synthetic fun cancel (Lkotlinx/coroutines/Deferred;)Z + public static synthetic fun cancel (Lkotlinx/coroutines/Deferred;)V public static fun fold (Lkotlinx/coroutines/Deferred;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; public static fun get (Lkotlinx/coroutines/Deferred;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; public static fun minusKey (Lkotlinx/coroutines/Deferred;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; @@ -258,6 +277,10 @@ public final class kotlinx/coroutines/EventLoopKt { public static final fun processNextEventInCurrentThread ()J } +public final class kotlinx/coroutines/ExceptionsKt { + public static final fun CancellationException (Ljava/lang/String;Ljava/lang/Throwable;)Ljava/util/concurrent/CancellationException; +} + public abstract class kotlinx/coroutines/ExecutorCoroutineDispatcher : kotlinx/coroutines/CoroutineDispatcher, java/io/Closeable { public fun ()V public abstract fun close ()V @@ -283,9 +306,9 @@ public abstract interface annotation class kotlinx/coroutines/InternalCoroutines public abstract interface class kotlinx/coroutines/Job : kotlin/coroutines/CoroutineContext$Element { public static final field Key Lkotlinx/coroutines/Job$Key; public abstract fun attachChild (Lkotlinx/coroutines/ChildJob;)Lkotlinx/coroutines/ChildHandle; - public abstract fun cancel ()V - public abstract synthetic fun cancel ()Z - public abstract fun cancel (Ljava/lang/Throwable;)Z + public abstract synthetic fun cancel ()V + public abstract synthetic fun cancel (Ljava/lang/Throwable;)Z + public abstract fun cancel (Ljava/util/concurrent/CancellationException;)V public abstract fun getCancellationException ()Ljava/util/concurrent/CancellationException; public abstract fun getChildren ()Lkotlin/sequences/Sequence; public abstract fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0; @@ -300,8 +323,9 @@ public abstract interface class kotlinx/coroutines/Job : kotlin/coroutines/Corou } public final class kotlinx/coroutines/Job$DefaultImpls { - public static synthetic fun cancel (Lkotlinx/coroutines/Job;)Z + public static synthetic fun cancel (Lkotlinx/coroutines/Job;)V public static synthetic fun cancel$default (Lkotlinx/coroutines/Job;Ljava/lang/Throwable;ILjava/lang/Object;)Z + public static synthetic fun cancel$default (Lkotlinx/coroutines/Job;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V public static fun fold (Lkotlinx/coroutines/Job;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; public static fun get (Lkotlinx/coroutines/Job;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; public static synthetic fun invokeOnCompletion$default (Lkotlinx/coroutines/Job;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/DisposableHandle; @@ -315,30 +339,42 @@ public final class kotlinx/coroutines/Job$Key : kotlin/coroutines/CoroutineConte public final class kotlinx/coroutines/JobKt { public static final fun DisposableHandle (Lkotlin/jvm/functions/Function0;)Lkotlinx/coroutines/DisposableHandle; - public static final fun Job (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job; + public static final fun Job (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/CompletableJob; + public static final synthetic fun Job (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job; + public static synthetic fun Job$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/CompletableJob; public static synthetic fun Job$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/Job; - public static final fun cancel (Lkotlin/coroutines/CoroutineContext;)V - public static final synthetic fun cancel (Lkotlin/coroutines/CoroutineContext;)Z - public static final fun cancel (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)Z + 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 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 final fun cancelAndJoin (Lkotlinx/coroutines/Job;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun cancelChildren (Lkotlin/coroutines/CoroutineContext;)V - public static final fun cancelChildren (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V - public static final fun cancelChildren (Lkotlinx/coroutines/Job;)V - public static final fun cancelChildren (Lkotlinx/coroutines/Job;Ljava/lang/Throwable;)V + public static final synthetic fun cancelChildren (Lkotlin/coroutines/CoroutineContext;)V + public static final synthetic fun cancelChildren (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V + public static final fun cancelChildren (Lkotlin/coroutines/CoroutineContext;Ljava/util/concurrent/CancellationException;)V + public static final synthetic fun cancelChildren (Lkotlinx/coroutines/Job;)V + public static final synthetic fun cancelChildren (Lkotlinx/coroutines/Job;Ljava/lang/Throwable;)V + public static final fun cancelChildren (Lkotlinx/coroutines/Job;Ljava/util/concurrent/CancellationException;)V public static synthetic fun cancelChildren$default (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;ILjava/lang/Object;)V + public static synthetic fun cancelChildren$default (Lkotlin/coroutines/CoroutineContext;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V public static synthetic fun cancelChildren$default (Lkotlinx/coroutines/Job;Ljava/lang/Throwable;ILjava/lang/Object;)V + public static synthetic fun cancelChildren$default (Lkotlinx/coroutines/Job;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V public static final fun cancelFutureOnCancellation (Lkotlinx/coroutines/CancellableContinuation;Ljava/util/concurrent/Future;)V public static final fun cancelFutureOnCompletion (Lkotlinx/coroutines/Job;Ljava/util/concurrent/Future;)Lkotlinx/coroutines/DisposableHandle; + public static final fun ensureActive (Lkotlin/coroutines/CoroutineContext;)V + public static final fun ensureActive (Lkotlinx/coroutines/Job;)V public static final fun isActive (Lkotlin/coroutines/CoroutineContext;)Z } public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlinx/coroutines/Job, kotlinx/coroutines/ParentJob, kotlinx/coroutines/selects/SelectClause0 { public fun (Z)V + protected fun afterCompletionInternal (Ljava/lang/Object;I)V public final fun attachChild (Lkotlinx/coroutines/ChildJob;)Lkotlinx/coroutines/ChildHandle; - public fun cancel ()V - public synthetic fun cancel ()Z - public fun cancel (Ljava/lang/Throwable;)Z + public synthetic fun cancel ()V + public synthetic fun cancel (Ljava/lang/Throwable;)Z + public fun cancel (Ljava/util/concurrent/CancellationException;)V + public final fun cancelCoroutine (Ljava/lang/Throwable;)Z + public fun cancelInternal (Ljava/lang/Throwable;)Z public fun childCancelled (Ljava/lang/Throwable;)Z public fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; @@ -347,11 +383,12 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin public fun getChildJobCancellationCause ()Ljava/lang/Throwable; public final fun getChildren ()Lkotlin/sequences/Sequence; protected final fun getCompletionCause ()Ljava/lang/Throwable; + protected final fun getCompletionCauseHandled ()Z public final fun getCompletionExceptionOrNull ()Ljava/lang/Throwable; protected fun getHandlesException ()Z public final fun getKey ()Lkotlin/coroutines/CoroutineContext$Key; public final fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0; - protected fun handleJobException (Ljava/lang/Throwable;)V + protected fun handleJobException (Ljava/lang/Throwable;)Z public final fun invokeOnCompletion (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle; public final fun invokeOnCompletion (ZZLkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle; public fun isActive ()Z @@ -360,12 +397,15 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin public final fun isCompletedExceptionally ()Z public final fun join (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; - protected fun onCancellation (Ljava/lang/Throwable;)V + protected fun onCancelling (Ljava/lang/Throwable;)V + protected fun onCompletionInternal (Ljava/lang/Object;)V public final fun parentCancelled (Lkotlinx/coroutines/ParentJob;)V public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; public fun plus (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job; public final fun registerSelectClause0 (Lkotlinx/coroutines/selects/SelectInstance;Lkotlin/jvm/functions/Function1;)V public final fun start ()Z + protected final fun toCancellationException (Ljava/lang/Throwable;Ljava/lang/String;)Ljava/util/concurrent/CancellationException; + public static synthetic fun toCancellationException$default (Lkotlinx/coroutines/JobSupport;Ljava/lang/Throwable;Ljava/lang/String;ILjava/lang/Object;)Ljava/util/concurrent/CancellationException; public final fun toDebugString ()Ljava/lang/String; public fun toString ()Ljava/lang/String; } @@ -378,9 +418,9 @@ public abstract class kotlinx/coroutines/MainCoroutineDispatcher : kotlinx/corou public final class kotlinx/coroutines/NonCancellable : kotlin/coroutines/AbstractCoroutineContextElement, kotlinx/coroutines/Job { public static final field INSTANCE Lkotlinx/coroutines/NonCancellable; public fun attachChild (Lkotlinx/coroutines/ChildJob;)Lkotlinx/coroutines/ChildHandle; - public fun cancel ()V - public synthetic fun cancel ()Z - public fun cancel (Ljava/lang/Throwable;)Z + public synthetic fun cancel ()V + public synthetic fun cancel (Ljava/lang/Throwable;)Z + public fun cancel (Ljava/util/concurrent/CancellationException;)V public fun getCancellationException ()Ljava/util/concurrent/CancellationException; public fun getChildren ()Lkotlin/sequences/Sequence; public fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0; @@ -409,7 +449,7 @@ public abstract interface class kotlinx/coroutines/ParentJob : kotlinx/coroutine } public final class kotlinx/coroutines/ParentJob$DefaultImpls { - public static synthetic fun cancel (Lkotlinx/coroutines/ParentJob;)Z + public static synthetic fun cancel (Lkotlinx/coroutines/ParentJob;)V public static fun fold (Lkotlinx/coroutines/ParentJob;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; public static fun get (Lkotlinx/coroutines/ParentJob;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; public static fun minusKey (Lkotlinx/coroutines/ParentJob;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; @@ -422,7 +462,9 @@ public final class kotlinx/coroutines/RunnableKt { } public final class kotlinx/coroutines/SupervisorKt { - public static final fun SupervisorJob (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job; + public static final fun SupervisorJob (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/CompletableJob; + public static final synthetic fun SupervisorJob (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job; + public static synthetic fun SupervisorJob$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/CompletableJob; public static synthetic fun SupervisorJob$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/Job; public static final fun supervisorScope (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -442,6 +484,8 @@ public final class kotlinx/coroutines/ThreadContextElement$DefaultImpls { public final class kotlinx/coroutines/ThreadContextElementKt { public static final fun asContextElement (Ljava/lang/ThreadLocal;Ljava/lang/Object;)Lkotlinx/coroutines/ThreadContextElement; public static synthetic fun asContextElement$default (Ljava/lang/ThreadLocal;Ljava/lang/Object;ILjava/lang/Object;)Lkotlinx/coroutines/ThreadContextElement; + public static final fun ensurePresent (Ljava/lang/ThreadLocal;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun isPresent (Ljava/lang/ThreadLocal;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class kotlinx/coroutines/ThreadPoolDispatcherKt { @@ -471,16 +515,18 @@ public abstract interface class kotlinx/coroutines/channels/ActorScope : kotlinx } public final class kotlinx/coroutines/channels/ActorScope$DefaultImpls { - public static synthetic fun cancel (Lkotlinx/coroutines/channels/ActorScope;)Z + public static synthetic fun cancel (Lkotlinx/coroutines/channels/ActorScope;)V } public abstract interface class kotlinx/coroutines/channels/BroadcastChannel : kotlinx/coroutines/channels/SendChannel { - public abstract fun cancel (Ljava/lang/Throwable;)Z + public abstract synthetic fun cancel (Ljava/lang/Throwable;)Z + public abstract fun cancel (Ljava/util/concurrent/CancellationException;)V public abstract fun openSubscription ()Lkotlinx/coroutines/channels/ReceiveChannel; } public final class kotlinx/coroutines/channels/BroadcastChannel$DefaultImpls { public static synthetic fun cancel$default (Lkotlinx/coroutines/channels/BroadcastChannel;Ljava/lang/Throwable;ILjava/lang/Object;)Z + public static synthetic fun cancel$default (Lkotlinx/coroutines/channels/BroadcastChannel;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V } public final class kotlinx/coroutines/channels/BroadcastChannelKt { @@ -502,7 +548,7 @@ public abstract interface class kotlinx/coroutines/channels/Channel : kotlinx/co } public final class kotlinx/coroutines/channels/Channel$DefaultImpls { - public static synthetic fun cancel (Lkotlinx/coroutines/channels/Channel;)Z + public static synthetic fun cancel (Lkotlinx/coroutines/channels/Channel;)V } public final class kotlinx/coroutines/channels/Channel$Factory { @@ -531,6 +577,7 @@ public final class kotlinx/coroutines/channels/ChannelsKt { public static final fun associateByTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun associateByTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun associateTo (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Map;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun cancelConsumed (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/lang/Throwable;)V public static final fun consume (Lkotlinx/coroutines/channels/BroadcastChannel;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun consume (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun consumeEach (Lkotlinx/coroutines/channels/BroadcastChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -650,7 +697,8 @@ public final class kotlinx/coroutines/channels/ClosedSendChannelException : java public final class kotlinx/coroutines/channels/ConflatedBroadcastChannel : kotlinx/coroutines/channels/BroadcastChannel { public fun ()V public fun (Ljava/lang/Object;)V - public fun cancel (Ljava/lang/Throwable;)Z + public synthetic fun cancel (Ljava/lang/Throwable;)Z + public fun cancel (Ljava/util/concurrent/CancellationException;)V public fun close (Ljava/lang/Throwable;)Z public fun getOnSend ()Lkotlinx/coroutines/selects/SelectClause2; public final fun getValue ()Ljava/lang/Object; @@ -675,9 +723,9 @@ public abstract interface class kotlinx/coroutines/channels/ProducerScope : kotl } public abstract interface class kotlinx/coroutines/channels/ReceiveChannel { - public abstract fun cancel ()V - public abstract synthetic fun cancel ()Z - public abstract fun cancel (Ljava/lang/Throwable;)Z + public abstract synthetic fun cancel ()V + 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 getOnReceiveOrNull ()Lkotlinx/coroutines/selects/SelectClause1; public abstract fun isClosedForReceive ()Z @@ -689,8 +737,9 @@ public abstract interface class kotlinx/coroutines/channels/ReceiveChannel { } public final class kotlinx/coroutines/channels/ReceiveChannel$DefaultImpls { - public static synthetic fun cancel (Lkotlinx/coroutines/channels/ReceiveChannel;)Z + public static synthetic fun cancel (Lkotlinx/coroutines/channels/ReceiveChannel;)V public static synthetic fun cancel$default (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/lang/Throwable;ILjava/lang/Object;)Z + public static synthetic fun cancel$default (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/concurrent/CancellationException;ILjava/lang/Object;)V } public abstract interface class kotlinx/coroutines/channels/SendChannel { @@ -748,12 +797,14 @@ public final class kotlinx/coroutines/selects/SelectBuilder$DefaultImpls { public static fun invoke (Lkotlinx/coroutines/selects/SelectBuilder;Lkotlinx/coroutines/selects/SelectClause2;Lkotlin/jvm/functions/Function2;)V } -public final class kotlinx/coroutines/selects/SelectBuilderImpl : kotlinx/coroutines/internal/LockFreeLinkedListHead, kotlin/coroutines/Continuation, kotlinx/coroutines/selects/SelectBuilder, kotlinx/coroutines/selects/SelectInstance { +public final class kotlinx/coroutines/selects/SelectBuilderImpl : kotlinx/coroutines/internal/LockFreeLinkedListHead, kotlin/coroutines/Continuation, kotlin/coroutines/jvm/internal/CoroutineStackFrame, kotlinx/coroutines/selects/SelectBuilder, kotlinx/coroutines/selects/SelectInstance { public fun (Lkotlin/coroutines/Continuation;)V public fun disposeOnSelect (Lkotlinx/coroutines/DisposableHandle;)V + public fun getCallerFrame ()Lkotlin/coroutines/jvm/internal/CoroutineStackFrame; public fun getCompletion ()Lkotlin/coroutines/Continuation; public fun getContext ()Lkotlin/coroutines/CoroutineContext; public final fun getResult ()Ljava/lang/Object; + public fun getStackTraceElement ()Ljava/lang/StackTraceElement; public final fun handleBuilderException (Ljava/lang/Throwable;)V public fun invoke (Lkotlinx/coroutines/selects/SelectClause0;Lkotlin/jvm/functions/Function1;)V public fun invoke (Lkotlinx/coroutines/selects/SelectClause1;Lkotlin/jvm/functions/Function2;)V diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-debug.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-debug.txt index 3b05e69abc..183495af5c 100644 --- a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-debug.txt +++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-debug.txt @@ -1,11 +1,11 @@ -public final class kotlinx/coroutines/debug/CoroutineState { - public final fun component1 ()Lkotlin/coroutines/Continuation; - public final fun copy (Lkotlin/coroutines/Continuation;Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;J)Lkotlinx/coroutines/debug/CoroutineState; - public static synthetic fun copy$default (Lkotlinx/coroutines/debug/CoroutineState;Lkotlin/coroutines/Continuation;Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;JILjava/lang/Object;)Lkotlinx/coroutines/debug/CoroutineState; +public final class kotlinx/coroutines/debug/CoroutineInfo { + public final fun component1 ()Lkotlin/coroutines/CoroutineContext; + public final fun copy (Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;J)Lkotlinx/coroutines/debug/CoroutineInfo; + public static synthetic fun copy$default (Lkotlinx/coroutines/debug/CoroutineInfo;Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;JILjava/lang/Object;)Lkotlinx/coroutines/debug/CoroutineInfo; public fun equals (Ljava/lang/Object;)Z - public final fun getContinuation ()Lkotlin/coroutines/Continuation; + public final fun getContext ()Lkotlin/coroutines/CoroutineContext; public final fun getCreationStackTrace ()Ljava/util/List; - public final fun getJobOrNull ()Lkotlinx/coroutines/Job; + public final fun getJob ()Lkotlinx/coroutines/Job; public final fun getState ()Lkotlinx/coroutines/debug/State; public fun hashCode ()I public final fun lastObservedStackTrace ()Ljava/util/List; @@ -16,7 +16,7 @@ public final class kotlinx/coroutines/debug/DebugProbes { public static final field INSTANCE Lkotlinx/coroutines/debug/DebugProbes; public final fun dumpCoroutines (Ljava/io/PrintStream;)V public static synthetic fun dumpCoroutines$default (Lkotlinx/coroutines/debug/DebugProbes;Ljava/io/PrintStream;ILjava/lang/Object;)V - public final fun dumpCoroutinesState ()Ljava/util/List; + public final fun dumpCoroutinesInfo ()Ljava/util/List; public final fun getSanitizeStackTraces ()Z public final fun install ()V public final fun jobToString (Lkotlinx/coroutines/Job;)Ljava/lang/String; @@ -38,3 +38,15 @@ public final class kotlinx/coroutines/debug/State : java/lang/Enum { public static fun values ()[Lkotlinx/coroutines/debug/State; } +public final class kotlinx/coroutines/debug/junit4/CoroutinesTimeout : org/junit/rules/TestRule { + public static final field Companion Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout$Companion; + public fun (JZ)V + public synthetic fun (JZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun apply (Lorg/junit/runners/model/Statement;Lorg/junit/runner/Description;)Lorg/junit/runners/model/Statement; +} + +public final class kotlinx/coroutines/debug/junit4/CoroutinesTimeout$Companion { + public final fun seconds (IZ)Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout; + public static synthetic fun seconds$default (Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout$Companion;IZILjava/lang/Object;)Lkotlinx/coroutines/debug/junit4/CoroutinesTimeout; +} + diff --git a/binary-compatibility-validator/resources/api.properties b/binary-compatibility-validator/resources/api.properties index 3c41251efe..e4598617cd 100644 --- a/binary-compatibility-validator/resources/api.properties +++ b/binary-compatibility-validator/resources/api.properties @@ -2,8 +2,8 @@ # Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. # -module.roots=core integration native reactive ui test debug +module.roots=/ integration reactive ui module.marker=build.gradle -module.ignore=kotlinx-coroutines-rx-example stdlib-stubs +module.ignore=kotlinx-coroutines-rx-example stdlib-stubs benchmarks knit binary-compatibility-validator site packages.internal=kotlinx.coroutines.internal \ No newline at end of file diff --git a/binary-compatibility-validator/test/PublicApiTest.kt b/binary-compatibility-validator/test/PublicApiTest.kt index 7b8d4a8dec..8c2ca3fd2f 100644 --- a/binary-compatibility-validator/test/PublicApiTest.kt +++ b/binary-compatibility-validator/test/PublicApiTest.kt @@ -60,7 +60,8 @@ class PublicApiTest( it matches regex && !it.endsWith("-sources.jar") && !it.endsWith("-javadoc.jar") - && !it.endsWith("-tests.jar")} } + && !it.endsWith("-tests.jar")} + && !it.name.contains("-metadata-")} return files.singleOrNull() ?: throw Exception("No single file matching $regex in $libsDir:\n${files.joinToString("\n")}") } } diff --git a/build.gradle b/build.gradle index a6e81b7cbb..72ea1514fa 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,26 @@ /* - * 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 org.jetbrains.kotlin.konan.target.HostManager + +apply from: rootProject.file("gradle/experimental.gradle") + +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'] +// Not published +def unpublished = internal + ['kotlinx-coroutines-rx-example', 'example-frontend-js', 'android-unit-tests'] + +static def platformOf(project) { + def name = project.name + if (name.endsWith("-js")) return "js" + if (name.endsWith("-common") || name.endsWith("-native")) { + throw IllegalStateException("$name platform is not supported") + } + return "jvm" +} buildscript { ext.useKotlinSnapshot = rootProject.properties['kotlinSnapshot'] != null @@ -12,29 +32,16 @@ buildscript { } repositories { jcenter() - maven { url "https://teamcity.jetbrains.com/guestAuth/app/rest/builds/id:1907319/artifacts/content/maven" } maven { url "https://kotlin.bintray.com/kotlinx" } maven { url "https://kotlin.bintray.com/kotlin-dev" } maven { url "https://kotlin.bintray.com/kotlin-eap" } maven { url "https://jetbrains.bintray.com/kotlin-native-dependencies" } maven { url "https://plugins.gradle.org/m2/" } } - configurations.classpath { - resolutionStrategy { - eachDependency { DependencyResolveDetails details -> - if (details.requested.group == 'org.jetbrains.kotlin' && details.requested.name != 'kotlin-native-gradle-plugin') { - // fix version of all dependencies from org.jetbrains.kotlin group - // even when other dependencies require other versions indirectly, - // except kotlin-native, which has its own pre-release versioning - details.useVersion kotlin_version - } - } - } - } + dependencies { classpath "org.jfrog.buildinfo:build-info-extractor-gradle:$artifactory_plugin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:$kotlin_native_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" @@ -48,119 +55,90 @@ buildscript { } allprojects { + // the only place where HostManager could be instantiated + project.ext.hostManager = new HostManager() +} + +allprojects { + apply plugin: 'kotlinx-atomicfu' + def deployVersion = properties['DeployVersion'] if (deployVersion != null) version = deployVersion if (useKotlinSnapshot) { kotlin_version = '1.2-SNAPSHOT' } - def name = it.name + def projectName = it.name repositories { /* * google should be first in the repository list because some of the play services * transitive dependencies was removed from jcenter, thus breaking gradle dependency resolution */ - if (name == "kotlinx-coroutines-play-services") { + if (projectName == "kotlinx-coroutines-play-services") { google() } jcenter() - maven { url "https://teamcity.jetbrains.com/guestAuth/app/rest/builds/id:1907319/artifacts/content/maven" } maven { url "https://kotlin.bintray.com/kotlin-dev" } maven { url "https://kotlin.bintray.com/kotlin-eap" } maven { url "https://kotlin.bintray.com/kotlinx" } } -} + if (projectName == rootModule || projectName == coreModule) return -// Report Kotlin compiler version when building project -println("Using Kotlin compiler version: $org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION") + // Add dependency to core source sets. Core is configured in kx-core/build.gradle + evaluationDependsOn(":$coreModule") + if (sourceless.contains(projectName)) return -// --------------- Configure sub-projects with Kotlin sources --------------- + def platform = platformOf(it) + apply from: rootProject.file("gradle/compile-${platform}.gradle") -def sourceless = ['site'] -static def platformOf(project) { - if (project.name.endsWith("-common")) return "common" - if (project.name.endsWith("-js")) return "js" - if (project.name.endsWith("-native")) return "native" - return "jvm" -} + dependencies { + // See comment below for rationale, it will be replaced with "project" dependency + compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version" + compileOnly "org.jetbrains.kotlinx:atomicfu:$atomicFU_version" -static def platformLib(base, platform) { - if (platform == "jvm") return base - return "$base-$platform" -} + // the only way IDEA can resolve test classes + testCompile project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs + } -subprojects { - if (useKotlinSnapshot) { - repositories { - maven { url "https://oss.sonatype.org/content/repositories/snapshots" } - } + tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all { + kotlinOptions.freeCompilerArgs += experimentalAnnotations.collect { "-Xuse-experimental=" + it } + kotlinOptions.freeCompilerArgs += "-progressive" + // Binary compatibility support + kotlinOptions.freeCompilerArgs += ["-Xdump-declarations-to=${buildDir}/visibilities.json"] } +} + +/* + * Hack to trick nmpp plugin: we are renaming artifacts in order to provide backward compatibility for dependencies, + * but publishing plugin does not re-read artifact names for kotlin-jvm projects, so renaming is not applied in pom files + * for JVM-only projects. + * + * We artificially replace "project" dependency with "module" one to have proper names in pom files, but then substitute it + * to have out "project" dependency back. + */ +configure(subprojects.findAll { it.name != coreModule && it.name != rootModule }) { configurations.all { - resolutionStrategy { - eachDependency { DependencyResolveDetails details -> - if (details.requested.group == 'org.jetbrains.kotlin') { - details.useVersion kotlin_version - } - } + resolutionStrategy.dependencySubstitution { + substitute module("org.jetbrains.kotlinx:kotlinx-coroutines-core:$version") with project(':kotlinx-coroutines-core') } } } -configure(subprojects.findAll { !sourceless.contains(it.name) }) { - def platform = platformOf(it) - apply from: rootProject.file("gradle/compile-${platform}.gradle") -} - -// --------------- Configure sub-projects that are part of the library --------------- - -def internal = sourceless + ['benchmarks', 'knit', 'js-stub', 'stdlib-stubs', 'binary-compatibility-validator'] - -// Reconfigure source sets to avoid long "src/main/kotlin/fqn" -configure(subprojects.findAll { !it.name.contains(sourceless) && it.name != "benchmarks" }) { - def projectName = it.name +// Redefine source sets because we are not using 'kotlin/main/fqn' folder convention +configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != "benchmarks" && it.name != coreModule }) { sourceSets { main.kotlin.srcDirs = ['src'] test.kotlin.srcDirs = ['test'] - // todo: do we still need this workaround? - if (!projectName.endsWith("-native")) { - main.resources.srcDirs = ['resources'] - test.resources.srcDirs = ['test-resources'] - } - } -} - -// configure atomicfu -configure(subprojects.findAll { !internal.contains(it.name) }) { - def platform = platformOf(it) - apply from: rootProject.file("gradle/atomicfu-${platform}.gradle") -} - -// configure dependencies on core -configure(subprojects.findAll { !internal.contains(it.name) && it.name != 'kotlinx-coroutines-core-common'}) { - def platform = platformOf(it) - def coroutines_core = platformLib("kotlinx-coroutines-core", platform) + main.resources.srcDirs = ['resources'] + test.resources.srcDirs = ['test-resources'] - if (it.name == coroutines_core) { - dependencies { - expectedBy project(':kotlinx-coroutines-core-common') - } - } else { - dependencies { - compile project(":$coroutines_core") - //the only way IDEA can resolve test classes - testCompile project(":$coroutines_core").sourceSets.test.output - } } } -// --------------- Configure sub-projects that are published --------------- - -def unpublished = internal + ['kotlinx-coroutines-rx-example', 'example-frontend-js', 'android-unit-tests'] - -def core_docs_url = "https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/" -def core_docs_file = "$projectDir/core/kotlinx-coroutines-core/build/dokka/kotlinx-coroutines-core/package-list" +def core_docs_url = "https://kotlin.github.io/kotlinx.coroutines/$coreModule/" +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') @@ -168,12 +146,8 @@ configure(subprojects.findAll { !unpublished.contains(it.name) }) { } configure(subprojects.findAll { !unpublished.contains(it.name) }) { - def platform = platformOf(it) - def coroutines_core = platformLib("kotlinx-coroutines-core", platform) - - if (it.name != coroutines_core) { - dokka.dependsOn project(":$coroutines_core").dokka - + if (it.name != coreModule) { + dokka.dependsOn project(":$coreModule").dokka tasks.withType(dokka.getClass()) { externalDocumentationLink { url = new URL(core_docs_url) @@ -182,27 +156,16 @@ configure(subprojects.findAll { !unpublished.contains(it.name) }) { } } - if (platform == "jvm") { - dokkaJavadoc.dependsOn project(":$coroutines_core").dokka - // dump declarations from main JVM module for binary-compatibility-validator - compileKotlin { - kotlinOptions.freeCompilerArgs += ["-Xdump-declarations-to=${buildDir}/visibilities.json"] - } + if (platformOf(it) == "jvm") { + dokkaJavadoc.dependsOn project(":$coreModule").dokka } +} - tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all { - kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental", - "-Xuse-experimental=kotlin.experimental.ExperimentalTypeInference", - "-Xuse-experimental=kotlin.ExperimentalMultiplatform", - "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi", - "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi", - "-progressive"] - } -} +// Report Kotlin compiler version when building project +println("Using Kotlin compiler version: $org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION") -// main deployment task +// --------------- Configure sub-projects that are published --------------- task deploy(dependsOn: getTasksByName("bintrayUpload", true) + getTasksByName("publishNpm", true)) apply plugin: 'base' diff --git a/bump-version.sh b/bump-version.sh index ab1649759e..00930cbd49 100755 --- a/bump-version.sh +++ b/bump-version.sh @@ -16,11 +16,10 @@ update_version() { } update_version "README.md" -update_version "core/kotlinx-coroutines-debug/README.md" -update_version "core/kotlinx-coroutines-test/README.md" +update_version "kotlinx-coroutines-core/README.md" +update_version "kotlinx-coroutines-debug/README.md" +update_version "kotlinx-coroutines-test/README.md" update_version "ui/coroutines-guide-ui.md" -update_version "ui/coroutines-guide-ui.md" -update_version "native/README.md" update_version "ui/kotlinx-coroutines-android/example-app/gradle.properties" update_version "ui/kotlinx-coroutines-android/animation-app/gradle.properties" update_version "gradle.properties" diff --git a/common/README.md b/common/README.md deleted file mode 100644 index 07dd61b491..0000000000 --- a/common/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Common coroutines core - -This directory contains modules that provide `expect` declarations for common coroutines support across -various platforms for [mutliplatform Kotlin projects](https://kotlinlang.org/docs/reference/multiplatform.html). -Note, that documentation is currently provided in platform-specific modules only. -Module name below corresponds to the artifact name in Maven/Gradle. - -## Modules - -* [kotlinx-coroutines-core-common](kotlinx-coroutines-core-common/README.md) -- common declarations for coroutine builders and primitives. - diff --git a/common/kotlinx-coroutines-core-common/README.md b/common/kotlinx-coroutines-core-common/README.md deleted file mode 100644 index 7ff030e799..0000000000 --- a/common/kotlinx-coroutines-core-common/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Module kotlinx-coroutines-core-js - -Common primitives to work with coroutines in -[multiplatform Kotlin projects](https://kotlinlang.org/docs/reference/multiplatform.html). - -Documentation is provided in platform-specific modules: -* [kotlinx-coroutines-core](../../core/kotlinx-coroutines-core/README.md) for Kotlin/JVM. -* [kotlinx-coroutines-core-js](../../js/kotlinx-coroutines-core-js/README.md) for Kotlin/JS. -* [kotlinx-coroutines-core-native](../../native/kotlinx-coroutines-core-native/README.md) for Kotlin/Native. diff --git a/common/kotlinx-coroutines-core-common/build.gradle b/common/kotlinx-coroutines-core-common/build.gradle deleted file mode 100644 index 3b17101f53..0000000000 --- a/common/kotlinx-coroutines-core-common/build.gradle +++ /dev/null @@ -1,4 +0,0 @@ -/* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - diff --git a/common/kotlinx-coroutines-core-common/src/channels/ChannelCoroutine.kt b/common/kotlinx-coroutines-core-common/src/channels/ChannelCoroutine.kt deleted file mode 100644 index f69c69b34a..0000000000 --- a/common/kotlinx-coroutines-core-common/src/channels/ChannelCoroutine.kt +++ /dev/null @@ -1,31 +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.channels - -import kotlinx.coroutines.* -import kotlin.coroutines.* - -@Suppress("DEPRECATION") -internal open class ChannelCoroutine( - parentContext: CoroutineContext, - protected val _channel: Channel, - active: Boolean -) : AbstractCoroutine(parentContext, active), Channel by _channel { - override val cancelsParent: Boolean get() = true - - val channel: Channel get() = this - - override fun cancel() { - cancel(null) - } - - override fun cancel0(): Boolean = cancel(null) - - override fun cancel(cause: Throwable?): Boolean { - val wasCancelled = _channel.cancel(cause) - if (wasCancelled) super.cancel(cause) // cancel the job - return wasCancelled - } -} diff --git a/core/README.md b/core/README.md deleted file mode 100644 index 5719a8628b..0000000000 --- a/core/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Coroutines core for Kotlin/JVM - -This directory contains modules that provide core coroutines support on Kotlin/JVM. -Module name below corresponds to the artifact name in Maven/Gradle. - -## Modules - -* [kotlinx-coroutines-core](kotlinx-coroutines-core/README.md) — core coroutine builders and synchronization primitives. -* [kotlinx-coroutines-debug](kotlinx-coroutines-debug/README.md) — coroutines debug utilities. -* [kotlinx-coroutines-test](kotlinx-coroutines-test/README.md) — coroutines test utilities. - diff --git a/core/kotlinx-coroutines-core/build.gradle b/core/kotlinx-coroutines-core/build.gradle deleted file mode 100644 index 7e3c0bf47e..0000000000 --- a/core/kotlinx-coroutines-core/build.gradle +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -dependencies { - testCompile "com.devexperts.lincheck:lincheck:$lincheck_version" - testCompile "com.esotericsoftware:kryo:4.0.0" -} - -task checkJdk16() { - // only fail w/o JDK_16 when actually trying to compile, not during project setup phase - doLast { - if (!System.env.JDK_16) { - throw new GradleException("JDK_16 environment variable is not defined. " + - "Can't build against JDK 1.6 runtime and run JDK 1.6 compatibility tests. " + - "Please ensure JDK 1.6 is installed and that JDK_16 points to it.") - } - } -} - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { - kotlinOptions.jdkHome = System.env.JDK_16 - // only fail when actually trying to compile, not during project setup phase - dependsOn(checkJdk16) -} - -tasks.withType(Test) { - minHeapSize = '1g' - maxHeapSize = '1g' - enableAssertions = true - systemProperty 'java.security.manager', 'kotlinx.coroutines.TestSecurityManager' -} - -test { - exclude '**/*LFTest.*' - systemProperty 'kotlinx.coroutines.scheduler.keep.alive.sec', '100000' // any unpark problem hangs test -} - -task lockFreedomTest(type: Test, dependsOn: testClasses) { - classpath = files(configurations.testRuntime, - sourceSets.main.output.classesDirs, //clear, untransformed classes - sourceSets.test.output.classesDirs) - include '**/*LFTest.*' -} - -task jdk16Test(type: Test, dependsOn: [testClasses, checkJdk16]) { - executable = "$System.env.JDK_16/bin/java" - exclude '**/*LinearizabilityTest.*' - exclude '**/*LFTest.*' - exclude '**/exceptions/**' - exclude '**/ExceptionsGuideTest.*' -} - -// Run these tests only during nightly stress test -jdk16Test.onlyIf { project.properties['stressTest'] != null } - -// Always run those tests -task moreTest(dependsOn: [lockFreedomTest, jdk16Test]) - -build.dependsOn moreTest - -task testsJar(type: Jar, dependsOn: testClasses) { - classifier = 'tests' - from sourceSets.test.output -} - -artifacts { - archives testsJar -} diff --git a/core/kotlinx-coroutines-core/src/internal/ExceptionsConstuctor.kt b/core/kotlinx-coroutines-core/src/internal/ExceptionsConstuctor.kt deleted file mode 100644 index 5695060785..0000000000 --- a/core/kotlinx-coroutines-core/src/internal/ExceptionsConstuctor.kt +++ /dev/null @@ -1,45 +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 java.util.* -import java.util.concurrent.locks.* -import kotlin.concurrent.* - -private val cacheLock = ReentrantReadWriteLock() -// Replace it with ClassValue when Java 6 support is over -private val exceptionConstructors: WeakHashMap, (Throwable) -> Throwable?> = WeakHashMap() - -@Suppress("UNCHECKED_CAST") -internal fun tryCopyException(exception: E): E? { - val cachedCtor = cacheLock.read { - exceptionConstructors[exception.javaClass] - } - - if (cachedCtor != null) return cachedCtor(exception) as E? - - /* - * Try to reflectively find constructor(), constructor(message, cause) or constructor(cause). - * Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace. - */ - var ctor: ((Throwable) -> Throwable?)? = null - val constructors = exception.javaClass.constructors.sortedByDescending { it.parameterTypes.size } - for (constructor in constructors) { - val parameters = constructor.parameterTypes - if (parameters.size == 2 && parameters[0] == String::class.java && parameters[1] == Throwable::class.java) { - ctor = { e -> runCatching { constructor.newInstance(e.message, e) as E }.getOrNull() } - break - } else if (parameters.size == 1 && parameters[0] == Throwable::class.java) { - ctor = { e -> runCatching { constructor.newInstance(e) as E }.getOrNull() } - break - } else if (parameters.isEmpty()) { - ctor = { e -> runCatching { constructor.newInstance() as E }.getOrNull()?.also { it.initCause(e) } } - break - } - } - - cacheLock.write { exceptionConstructors[exception.javaClass] = (ctor ?: { null }) } - return ctor?.invoke(exception) as E? -} diff --git a/core/kotlinx-coroutines-core/src/internal/SystemProps.kt b/core/kotlinx-coroutines-core/src/internal/SystemProps.kt deleted file mode 100644 index cbef51f48e..0000000000 --- a/core/kotlinx-coroutines-core/src/internal/SystemProps.kt +++ /dev/null @@ -1,50 +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 - -// number of processors at startup for consistent prop initialization -internal val AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors() - -internal fun systemProp( - propertyName: String -): String? = - try { - System.getProperty(propertyName) - } catch (e: SecurityException) { - null - } - -internal fun systemProp( - propertyName: String, - defaultValue: Boolean -): Boolean = - try { - System.getProperty(propertyName)?.toBoolean() ?: defaultValue - } catch (e: SecurityException) { - defaultValue - } - -internal fun systemProp( - propertyName: String, - defaultValue: Int, - minValue: Int = 1, - maxValue: Int = Int.MAX_VALUE -): Int - = systemProp(propertyName, defaultValue.toLong(), minValue.toLong(), maxValue.toLong()).toInt() - -internal fun systemProp( - propertyName: String, - defaultValue: Long, - minValue: Long = 1, - maxValue: Long = Long.MAX_VALUE -): Long { - val value = systemProp(propertyName) ?: return defaultValue - val parsed = value.toLongOrNull() - ?: error("System property '$propertyName' has unrecognized value '$value'") - if (parsed !in minValue..maxValue) { - error("System property '$propertyName' should be in range $minValue..$maxValue, but is '$parsed'") - } - return parsed -} diff --git a/core/kotlinx-coroutines-core/test/exceptions/Stacktraces.kt b/core/kotlinx-coroutines-core/test/exceptions/Stacktraces.kt deleted file mode 100644 index 51e71d5138..0000000000 --- a/core/kotlinx-coroutines-core/test/exceptions/Stacktraces.kt +++ /dev/null @@ -1,47 +0,0 @@ -package kotlinx.coroutines.exceptions - -import java.io.* -import kotlin.test.* - -public fun verifyStackTrace(e: Throwable, vararg traces: String) { - val stacktrace = toStackTrace(e) - traces.forEach { - assertTrue( - stacktrace.trimStackTrace().contains(it.trimStackTrace()), - "\nExpected trace element:\n$it\n\nActual stacktrace:\n$stacktrace" - ) - } - - val causes = stacktrace.count("Caused by") - assertNotEquals(0, causes) - assertEquals(traces.map { it.count("Caused by") }.sum(), causes) -} - -public fun toStackTrace(t: Throwable): String { - val sw = StringWriter() as Writer - t.printStackTrace(PrintWriter(sw)) - return sw.toString() -} - -public fun String.trimStackTrace(): String { - return applyBackspace(trimIndent().replace(Regex(":[0-9]+"), "") - .replace("kotlinx_coroutines_core_main", "") // yay source sets - .replace("kotlinx_coroutines_core", "")) -} - -public fun applyBackspace(line: String): String { - val array = line.toCharArray() - val stack = CharArray(array.size) - var stackSize = -1 - for (c in array) { - if (c != '\b') { - stack[++stackSize] = c - } else { - --stackSize - } - } - - return String(stack, 0, stackSize) -} - -public fun String.count(substring: String): Int = split(substring).size - 1 \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/test/exceptions/WithContextCancellationStressTest.kt b/core/kotlinx-coroutines-core/test/exceptions/WithContextCancellationStressTest.kt deleted file mode 100644 index 6760927a80..0000000000 --- a/core/kotlinx-coroutines-core/test/exceptions/WithContextCancellationStressTest.kt +++ /dev/null @@ -1,117 +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.exceptions - -import kotlinx.coroutines.* -import org.junit.* -import org.junit.Test -import java.io.* -import java.util.concurrent.* -import kotlin.coroutines.* -import kotlin.test.* - -class WithContextCancellationStressTest : TestBase() { - - private val iterations = 15_000 * stressTestMultiplier - private val pool = newFixedThreadPoolContext(3, "WithContextCancellationStressTest") - - @After - fun tearDown() { - pool.close() - } - - @Test - @Suppress("DEPRECATION") - fun testConcurrentCancellation() = runBlocking { - var ioException = 0 - var arithmeticException = 0 - var aioobException = 0 - - repeat(iterations) { - val barrier = CyclicBarrier(4) - val ctx = pool + NonCancellable - val jobWithContext = async(ctx) { - withContext(wrapperDispatcher(coroutineContext)) { - barrier.await() - throw IOException() - } - } - - val cancellerJob = async(ctx) { - barrier.await() - jobWithContext.cancel(ArithmeticException()) - } - - val cancellerJob2 = async(ctx) { - barrier.await() - jobWithContext.cancel(ArrayIndexOutOfBoundsException()) - } - - barrier.await() - val aeCancelled = cancellerJob.await() - val aioobCancelled = cancellerJob2.await() - - try { - jobWithContext.await() - } catch (e: Exception) { - when (e) { - is IOException -> { - ++ioException - e.checkSuppressed(aeException = aeCancelled, aioobException = aioobCancelled) - } - is ArithmeticException -> { - ++arithmeticException - e.checkSuppressed(ioException = true, aioobException = aioobCancelled) - } - is ArrayIndexOutOfBoundsException -> { - ++aioobException - e.checkSuppressed(ioException = true, aeException = aeCancelled) - } - else -> error("Unexpected exception $e") - } - } - } - - require(ioException > 0) { "At least one IOException expected" } - require(arithmeticException > 0) { "At least one ArithmeticException expected" } - require(aioobException > 0) { "At least one ArrayIndexOutOfBoundsException expected" } - } - - private fun wrapperDispatcher(context: CoroutineContext): CoroutineContext { - val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher - return object : CoroutineDispatcher() { - override fun dispatch(context: CoroutineContext, block: Runnable) { - dispatcher.dispatch(context, block) - } - } - } - - private fun Throwable.checkSuppressed( - ioException: Boolean = false, - aeException: Boolean = false, - aioobException: Boolean = false - ) { - val suppressed: Array = suppressed - - try { - if (ioException) { - assertTrue(suppressed.any { it is IOException }, "IOException should be present: $this") - } - - if (aeException) { - assertTrue(suppressed.any { it is ArithmeticException }, "ArithmeticException should be present: $this") - } - - if (aioobException) { - assertTrue( - suppressed.any { it is ArrayIndexOutOfBoundsException }, - "ArrayIndexOutOfBoundsException should be present: $this" - ) - } - } catch (e: Throwable) { - val a =2 - } - } -} diff --git a/core/kotlinx-coroutines-debug/src/internal/DebugProbesImpl.kt b/core/kotlinx-coroutines-debug/src/internal/DebugProbesImpl.kt deleted file mode 100644 index 5c1aec1c7b..0000000000 --- a/core/kotlinx-coroutines-debug/src/internal/DebugProbesImpl.kt +++ /dev/null @@ -1,257 +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.debug.internal - -import kotlinx.coroutines.* -import kotlinx.coroutines.debug.* -import kotlinx.coroutines.internal.artificialFrame -import net.bytebuddy.* -import net.bytebuddy.agent.* -import net.bytebuddy.dynamic.loading.* -import java.io.* -import java.text.* -import java.util.* -import kotlin.collections.ArrayList -import kotlin.coroutines.* -import kotlin.coroutines.jvm.internal.* - -/** - * Mirror of [DebugProbes] with actual implementation. - * [DebugProbes] are implemented with pimpl to simplify user-facing class and make it look simple and - * documented. - */ -internal object DebugProbesImpl { - private const val ARTIFICIAL_FRAME_MESSAGE = "Coroutine creation stacktrace" - private val dateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss") - private val capturedCoroutines = WeakHashMap, CoroutineState>() - @Volatile - private var installations = 0 - private val isInstalled: Boolean get() = installations > 0 - // To sort coroutines by creation order, used as unique id - private var sequenceNumber: Long = 0 - - @Synchronized - public fun install() { - if (++installations > 1) return - - ByteBuddyAgent.install() - val cl = Class.forName("kotlin.coroutines.jvm.internal.DebugProbesKt") - val cl2 = Class.forName("kotlinx.coroutines.debug.DebugProbesKt") - - ByteBuddy() - .redefine(cl2) - .name(cl.name) - .make() - .load(cl.classLoader, ClassReloadingStrategy.fromInstalledAgent()) - } - - @Synchronized - public fun uninstall() { - check(isInstalled) { "Agent was not installed" } - if (--installations != 0) return - - capturedCoroutines.clear() - val cl = Class.forName("kotlin.coroutines.jvm.internal.DebugProbesKt") - val cl2 = Class.forName("kotlinx.coroutines.debug.internal.NoOpProbesKt") - - ByteBuddy() - .redefine(cl2) - .name(cl.name) - .make() - .load(cl.classLoader, ClassReloadingStrategy.fromInstalledAgent()) - } - - @Synchronized - public fun hierarchyToString(job: Job): String { - check(isInstalled) { "Debug probes are not installed" } - val jobToStack = capturedCoroutines - .filterKeys { it.delegate.context[Job] != null } - .mapKeys { it.key.delegate.context[Job]!! } - return buildString { - job.build(jobToStack, this, "") - } - } - - private fun Job.build(map: Map, builder: StringBuilder, indent: String) { - val state = map[this] - builder.append(indent) - @Suppress("DEPRECATION_ERROR") - val str = if (this !is JobSupport) toString() else toDebugString() - if (state == null) { - @Suppress("INVISIBLE_REFERENCE") - if (this !is kotlinx.coroutines.internal.ScopeCoroutine<*>) { // Do not print scoped coroutines - builder.append("$str\n") - } - } else { - val element = state.lastObservedStackTrace().firstOrNull() - val contState = state.state - builder.append("$str, continuation is $contState at line $element\n") - } - for (child in children) { - child.build(map, builder, indent + "\t") - } - } - - @Synchronized - public fun dumpCoroutinesState(): List { - check(isInstalled) { "Debug probes are not installed" } - return capturedCoroutines.entries.asSequence() - .map { CoroutineState(it.key.delegate, it.value) } - .sortedBy { it.sequenceNumber } - .toList() - } - - public fun dumpCoroutines(out: PrintStream) { - check(isInstalled) { "Debug probes are not installed" } - // Avoid inference with other out/err invocations by creating a string first - dumpCoroutines().let { out.println(it) } - } - - @Synchronized - private fun dumpCoroutines(): String = buildString { - // Synchronization window can be reduce even more, but no need to do it here - append("Coroutines dump ${dateFormat.format(System.currentTimeMillis())}") - capturedCoroutines - .asSequence() - .sortedBy { it.value.sequenceNumber } - .forEach { (key, value) -> - val state = if (value.state == State.RUNNING) - "${value.state} (Last suspension stacktrace, not an actual stacktrace)" - else - value.state.toString() - - append("\n\nCoroutine $key, state: $state") - val observedStackTrace = value.lastObservedStackTrace() - if (observedStackTrace.isEmpty()) { - append("\n\tat ${artificialFrame(ARTIFICIAL_FRAME_MESSAGE)}") - printStackTrace(value.creationStackTrace) - } else { - printStackTrace(value.lastObservedStackTrace()) - } - } - } - - private fun StringBuilder.printStackTrace(frames: List) { - frames.forEach { frame -> - append("\n\tat $frame") - } - } - - internal fun probeCoroutineResumed(frame: Continuation<*>) = updateState(frame, State.RUNNING) - - internal fun probeCoroutineSuspended(frame: Continuation<*>) = updateState(frame, State.SUSPENDED) - - private fun updateState(frame: Continuation<*>, state: State) { - if (!isInstalled) return - // Find ArtificialStackFrame of the coroutine - val owner = frame.owner() - updateState(owner, frame, state) - } - - @Synchronized - private fun updateState(owner: ArtificialStackFrame<*>?, frame: Continuation<*>, state: State) { - val coroutineState = capturedCoroutines[owner] ?: return - coroutineState.updateState(state, frame) - } - - private fun Continuation<*>.owner(): ArtificialStackFrame<*>? = - (this as? CoroutineStackFrame)?.owner() - - private tailrec fun CoroutineStackFrame.owner(): ArtificialStackFrame<*>? = - if (this is ArtificialStackFrame<*>) this else callerFrame?.owner() - - internal fun probeCoroutineCreated(completion: Continuation): Continuation { - if (!isInstalled) return completion - /* - * If completion already has an owner, it means that we are in scoped coroutine (coroutineScope, withContext etc.), - * then piggyback on its already existing owner and do not replace completion - */ - val owner = completion.owner() - if (owner != null) return completion - /* - * Here we replace completion with a sequence of CoroutineStackFrame objects - * which represents creation stacktrace, thus making stacktrace recovery mechanism - * even more verbose (it will attach coroutine creation stacktrace to all exceptions), - * and then using this artificial frame as an identifier of coroutineSuspended/resumed calls. - */ - val stacktrace = sanitizeStackTrace(Exception()) - val frame = stacktrace.foldRight(null) { frame, acc -> - object : CoroutineStackFrame { - override val callerFrame: CoroutineStackFrame? = acc - override fun getStackTraceElement(): StackTraceElement = frame - } - } - return ArtificialStackFrame(completion, frame!!).also { - storeFrame(it, completion) - } - } - - @Synchronized - private fun storeFrame(frame: ArtificialStackFrame, completion: Continuation) { - capturedCoroutines[frame] = CoroutineState(completion, frame, ++sequenceNumber) - } - - @Synchronized - private fun probeCoroutineCompleted(coroutine: ArtificialStackFrame<*>) { - capturedCoroutines.remove(coroutine) - } - - private class ArtificialStackFrame( - @JvmField val delegate: Continuation, - frame: CoroutineStackFrame - ) : Continuation by delegate, CoroutineStackFrame by frame { - override fun resumeWith(result: Result) { - probeCoroutineCompleted(this) - delegate.resumeWith(result) - } - - override fun toString(): String = delegate.toString() - } - - private fun sanitizeStackTrace(throwable: T): List { - val stackTrace = throwable.stackTrace - val size = stackTrace.size - val probeIndex = stackTrace.indexOfLast { it.className == "kotlin.coroutines.jvm.internal.DebugProbesKt" } - - if (!DebugProbes.sanitizeStackTraces) { - return List(size - probeIndex) { - if (it == 0) artificialFrame(ARTIFICIAL_FRAME_MESSAGE) else stackTrace[it + probeIndex] - } - } - - /* - * Trim intervals of internal methods from the stacktrace (bounds are excluded from trimming) - * E.g. for sequence [e, i1, i2, i3, e, i4, e, i5, i6, e7] - * output will be [e, i1, i3, e, i4, e, i5, i7] - */ - val result = ArrayList(size - probeIndex + 1) - result += artificialFrame(ARTIFICIAL_FRAME_MESSAGE) - var includeInternalFrame = true - for (i in (probeIndex + 1) until size - 1) { - val element = stackTrace[i] - if (!element.isInternalMethod) { - includeInternalFrame = true - result += element - continue - } - - if (includeInternalFrame) { - result += element - includeInternalFrame = false - } else if (stackTrace[i + 1].isInternalMethod) { - continue - } else { - result += element - includeInternalFrame = true - } - - } - - result += stackTrace[size - 1] - return result - } - - private val StackTraceElement.isInternalMethod: Boolean get() = className.startsWith("kotlinx.coroutines") -} diff --git a/coroutines-guide.md b/coroutines-guide.md index b9c2a0b2ee..ad06cc100b 100644 --- a/coroutines-guide.md +++ b/coroutines-guide.md @@ -38,7 +38,7 @@ The main coroutines guide has moved to the [docs folder](docs/coroutines-guide.m * [Parental responsibilities](docs/coroutine-context-and-dispatchers.md#parental-responsibilities) * [Naming coroutines for debugging](docs/coroutine-context-and-dispatchers.md#naming-coroutines-for-debugging) * [Combining context elements](docs/coroutine-context-and-dispatchers.md#combining-context-elements) - * [Cancellation via explicit job](docs/coroutine-context-and-dispatchers.md#cancellation-via-explicit-job) + * [Coroutine scope](docs/coroutine-context-and-dispatchers.md#coroutine-scope) * [Thread-local data](docs/coroutine-context-and-dispatchers.md#thread-local-data) * [Exception handling](docs/exception-handling.md#exception-handling) diff --git a/docs/basics.md b/docs/basics.md index 1f772e67fd..6c077874ea 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -6,8 +6,8 @@ // This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit. package kotlinx.coroutines.guide.$$1$$2 --> - - + @@ -386,7 +386,7 @@ fun main() = runBlocking { -> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-basic-07.kt) +> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt) You can run and see that it prints three lines and terminates: diff --git a/docs/cancellation-and-timeouts.md b/docs/cancellation-and-timeouts.md index 571b80ce23..742eea0a06 100644 --- a/docs/cancellation-and-timeouts.md +++ b/docs/cancellation-and-timeouts.md @@ -6,8 +6,8 @@ // This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit. package kotlinx.coroutines.guide.$$1$$2 --> - - + - - + + + + +* [Compatibility](#compatibility) +* [Public API types](#public-api-types) + * [Experimental API](#experimental-api) + * [Obsolete API](#obsolete-api) + * [Internal API](#internal-api) + * [Stable API](#stable-api) + * [Deprecation cycle](#deprecation-cycle) +* [Using annotated API](#using-annotated-api) + * [Programmatically](#programmatically) + * [Gradle](#gradle) + * [Maven](#maven) + + + +## Compatibility +This document describes the compatibility policy of `kotlinx.coroutines` library since version 1.0.0 and semantics of compatibility-specific annotations. + + +## Public API types +`kotlinx.coroutines` public API comes in five flavours: stable, experimental, obsolete, internal and deprecated. +All public API except stable is marked with the corresponding annotation. + +### Experimental API +Experimental API is marked with [@ExperimentalCoroutinesApi][ExperimentalCoroutinesApi] annotation. +API is marked experimental when its design has potential open questions which may eventually lead to +either semantics changes of the API or its deprecation. + +By default, most of the new API is marked as experimental and becomes stable in one of the next major releases if no new issues arise. +Otherwise, either semantics is fixed without changes in ABI or API goes through deprecation cycle. + +When using experimental API may be dangerous: +* You are writing a library which depends on `kotlinx.coroutines` and want to use experimental coroutines API in a stable library API. +It may lead to undesired consequences when end users of your library update their `kotlinx.coroutines` version where experimental API +has slightly different semantics. +* You want to build core infrastructure of the application around experimental API. + +### Obsolete API +Obsolete API is marked with [@ObsoleteCoroutinesApi][ObsoleteCoroutinesApi] annotation. +Obsolete API is similar to experimental, but already known to have serious design flaws and its potential replacement, +but replacement is not yet implemented. + +The semantics of this API won't be changed, but it will go through a deprecation cycle as soon as the replacement is ready. + +### Internal API +Internal API is marked with [@InternalCoroutinesApi][InternalCoroutinesApi] or is part of `kotlinx.coroutines.internal` package. +This API has no guarantees on its stability, can and will be changed and/or removed in the future releases. +If you can't avoid using internal API, please report it to [issue tracker](https://github.com/Kotlin/kotlinx.coroutines/issues/new). + +### Stable API +Stable API is guaranteed to preserve its ABI and documented semantics. If at some point unfixable design flaws will be discovered, +this API will go through a deprecation cycle and remain binary compatible as long as possible. + +### Deprecation cycle +When some API is deprecated, it goes through multiple stages and there is at least one major release between stages. +* Feature is deprecated with compilation warning. Most of the time, proper replacement +(and corresponding `replaceWith` declaration) is provided to automatically migrate deprecated usages with a help of IntelliJ IDEA. +* Deprecation level is increased to `error` or `hidden`. It is no longer possible to compile new code against deprecated API, + though it is still present in the ABI. +* API is completely removed. While we give our best efforts not to do so and have no plans of removing any API, we still are leaving +this option in case of unforeseen problems such as security holes. + +## Using annotated API +All API annotations are [kotlin.Experimental](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-experimental/index.html). +It is done in order to produce compilation warning about using experimental or obsolete API. +Warnings can be disabled either programmatically for a specific call site or globally for the whole module. + +### Programmatically +For a specific call-site, warning can be disabled by using [UseExperimental](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-use-experimental/index.html) annotation: +```kotlin +@UseExperimental(ExperimentalCoroutinesApi::class) // Disables warning about experimental coroutines API +fun experimentalApiUsage() { + someKotlinxCoroutinesExperimentalMethod() +} +``` + +### Gradle +For the Gradle project, a warning can be disabled by passing a compiler flag in your `build.gradle` file: + +```groovy +tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all { + kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi"] +} + +``` + +### Maven +For the Maven project, a warning can be disabled by passing a compiler flag in your `pom.xml` file: +```xml + + kotlin-maven-plugin + org.jetbrains.kotlin + ... your configuration ... + + + -Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi + + + +``` + + + + +[ExperimentalCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-experimental-coroutines-api/index.html +[ObsoleteCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-obsolete-coroutines-api/index.html +[InternalCoroutinesApi]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-internal-coroutines-api/index.html + diff --git a/docs/composing-suspending-functions.md b/docs/composing-suspending-functions.md index f67fe5b7ed..456d269251 100644 --- a/docs/composing-suspending-functions.md +++ b/docs/composing-suspending-functions.md @@ -6,8 +6,8 @@ // This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit. package kotlinx.coroutines.guide.$$1$$2 --> - - + - - + @@ -81,7 +81,7 @@ fun main() = runBlocking { -> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-context-01.kt) +> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt) It produces the following output (maybe in different order): @@ -145,7 +145,7 @@ fun main() = runBlocking { -> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-context-02.kt) +> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt) Produces the output: @@ -202,7 +202,7 @@ fun main() = runBlocking { -> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-context-03.kt) +> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt) There are three coroutines. The main coroutine (#1) -- `runBlocking` one, and two coroutines computing deferred values `a` (#2) and `b` (#3). @@ -253,7 +253,7 @@ fun main() { -> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-context-04.kt) +> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt) It demonstrates several new techniques. One is using [runBlocking] with an explicitly specified context, and the other one is using [withContext] function to change a context of a coroutine while still staying in the @@ -289,7 +289,7 @@ fun main() = runBlocking { -> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-context-05.kt) +> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt) It produces something like that when running in [debug mode](#debugging-coroutines-and-threads): @@ -347,7 +347,7 @@ fun main() = runBlocking { -> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-context-06.kt) +> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt) The output of this code is: @@ -390,7 +390,7 @@ fun main() = runBlocking { -> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-context-07.kt) +> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt) The result is going to be: @@ -442,7 +442,7 @@ fun main() = runBlocking(CoroutineName("main")) { -> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-context-08.kt) +> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt) The output it produces with `-Dkotlinx.coroutines.debug` JVM option is similar to: @@ -477,7 +477,7 @@ fun main() = runBlocking { -> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-context-09.kt) +> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt) The output of this code with `-Dkotlinx.coroutines.debug` JVM option is: @@ -487,47 +487,43 @@ I'm working in thread DefaultDispatcher-worker-1 @test#2 -### Cancellation via explicit job +### Coroutine scope Let us put our knowledge about contexts, children and jobs together. Assume that our application has an object with a lifecycle, but that object is not a coroutine. For example, we are writing an Android application and launch various coroutines in the context of an Android activity to perform asynchronous operations to fetch and update data, do animations, etc. All of these coroutines must be cancelled when activity is destroyed -to avoid memory leaks. - -We manage a lifecycle of our coroutines by creating an instance of [Job] that is tied to -the lifecycle of our activity. A job instance is created using [Job()] factory function when -activity is created and it is cancelled when an activity is destroyed like this: +to avoid memory leaks. We, of course, can manipulate contexts and jobs manually to tie activity's +and coroutines lifecycles, but `kotlinx.coroutines` provides an abstraction that encapsulates that: [CoroutineScope]. +You should be already familiar with coroutine scope as all coroutine builders are declared as extensions on it. +We manage a lifecycle of our coroutines by creating an instance of [CoroutineScope] that is tied to +the lifecycle of our activity. `CoroutineScope` instance can be created by [CoroutineScope()] or [MainScope()] +factory functions. The former creates a general-purpose scope, while the latter creates scope for UI applications and uses +[Dispatchers.Main] as default dispatcher:
```kotlin -class Activity : CoroutineScope { - lateinit var job: Job - - fun create() { - job = Job() - } - +class Activity { + private val mainScope = MainScope() + fun destroy() { - job.cancel() + mainScope.cancel() } // to be continued ... ```
-We also implement [CoroutineScope] interface in this `Actvity` class. We only need to provide an override -for its [CoroutineScope.coroutineContext] property to specify the context for coroutines launched in its -scope. We combine the desired dispatcher (we used [Dispatchers.Default] in this example) and a job: +Alternatively, we can implement [CoroutineScope] interface in this `Actvity` class. The best way to do it is +to use delegation with default factory functions. +We also can combine the desired dispatcher (we used [Dispatchers.Default] in this example) with the scope:
```kotlin - // class Activity continues - override val coroutineContext: CoroutineContext - get() = Dispatchers.Default + job + class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) { // to be continued ... ``` @@ -566,23 +562,13 @@ onto the screen anymore if we wait: import kotlin.coroutines.* import kotlinx.coroutines.* -class Activity : CoroutineScope { - lateinit var job: Job - - fun create() { - job = Job() - } +class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) { fun destroy() { - job.cancel() + cancel() // Extension on CoroutineScope } // to be continued ... - // class Activity continues - override val coroutineContext: CoroutineContext - get() = Dispatchers.Default + job - // to be continued ... - // class Activity continues fun doSomething() { // launch ten coroutines for a demo, each working for a different time @@ -598,7 +584,6 @@ class Activity : CoroutineScope { fun main() = runBlocking { //sampleStart val activity = Activity() - activity.create() // create an activity activity.doSomething() // run test function println("Launched coroutines") delay(500L) // delay for half a second @@ -611,7 +596,7 @@ fun main() = runBlocking {
-> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-context-10.kt) +> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt) The output of this example is: @@ -650,7 +635,7 @@ fun main() = runBlocking { threadLocal.set("main") println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = "launch")) { - println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") + println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") yield() println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") } @@ -662,7 +647,7 @@ fun main() = runBlocking { -> You can get full code [here](../core/kotlinx-coroutines-core/test/guide/example-context-11.kt) +> You can get full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt) In this example we launch new coroutine in a background thread pool using [Dispatchers.Default], so it works on a different threads from a thread pool, but it still has the value of thread local variable, @@ -679,6 +664,10 @@ Post-main, current thread: Thread[main @coroutine#1,5,main], thread local value: +Note how easily one may forget the corresponding context element and then still safely access thread local. +To avoid such situations, it is recommended to use [ensurePresent] method +and fail-fast on improper usages. + `ThreadLocal` has first-class support and can be used with any primitive `kotlinx.coroutines` provides. It has one key limitation: when thread-local is mutated, a new value is not propagated to the coroutine caller (as context element cannot track all `ThreadLocal` object accesses) and updated value is lost on the next suspension. @@ -712,7 +701,10 @@ that should be implemented. [CoroutineScope.coroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/coroutine-context.html [Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html [CoroutineName]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html -[Job()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job.html +[CoroutineScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope.html +[MainScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html +[Dispatchers.Main]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html [asContextElement]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.lang.-thread-local/as-context-element.html +[ensurePresent]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/java.lang.-thread-local/ensure-present.html [ThreadContextElement]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-thread-context-element/index.html diff --git a/docs/coroutines-guide.md b/docs/coroutines-guide.md index 8756246007..06bbcdbcff 100644 --- a/docs/coroutines-guide.md +++ b/docs/coroutines-guide.md @@ -29,4 +29,4 @@ In order to use coroutines as well as follow the examples in this guide, you nee * [Guide to UI programming with coroutines](../ui/coroutines-guide-ui.md) * [Guide to reactive streams with coroutines](../reactive/coroutines-guide-reactive.md) * [Coroutines design document (KEEP)](https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md) -* [Full kotlinx.coroutines API reference](http://kotlin.github.io/kotlinx.coroutines) +* [Full kotlinx.coroutines API reference](https://kotlin.github.io/kotlinx.coroutines) diff --git a/docs/debugging.md b/docs/debugging.md new file mode 100644 index 0000000000..1067a37063 --- /dev/null +++ b/docs/debugging.md @@ -0,0 +1,96 @@ +**Table of contents** + + + +* [Debugging coroutines](#debugging-coroutines) +* [Debug mode](#debug-mode) +* [Stacktrace recovery](#stacktrace-recovery) + * [Stacktrace recovery machinery](#stacktrace-recovery-machinery) +* [Debug agent](#debug-agent) + * [Debug agent and Android](#debug-agent-and-android) + + + + +## Debugging coroutines +Debugging asynchronous programs is challenging, because multiple concurrent coroutines are typically working at the same time. +To help with that, `kotlinx.coroutines` comes with additional features for debugging: debug mode, stacktrace recovery +and debug agent. + +## Debug mode + +The first debugging feature of `kotlinx.coroutines` is debug mode. +It can be enabled either by setting system property [DEBUG_PROPERTY_NAME] or by running Java with enabled assertions (`-ea` flag). +The latter is helpful to have debug mode enabled by default in unit tests. + +Debug mode attaches a unique [name][CoroutineName] to every launched coroutine. +Coroutine name can be seen in a regular Java debugger, +in a string representation of the coroutine or in the thread name executing named coroutine. +Overhead of this feature is negligible and it can be safely turned on by default to simplify logging and diagnostic. + +## Stacktrace recovery + +Stacktrace recovery is another useful feature of debug mode. It is enabled by default in the debug mode, +but can be separately disabled by setting `kotlinx.coroutines.stacktrace.recovery` system property to `false`. + +Stacktrace recovery tries to stitch asynchronous exception stacktrace with a stacktrace of the receiver by copying it, providing +not only information where an exception was thrown, but also where it was asynchronously rethrown or caught. + +It is easy to demonstrate with actual stacktraces of the same program that awaits asynchronous operation in `main` function +(runnable code is [here](../kotlinx-coroutines-debug/test/RecoveryExample.kt)): + +| Without recovery | With recovery | +| - | - | +| ![before](images/before.png "before") | ![after](images/after.png "after") | + +The only downside of this approach is losing referential transparency of the exception. + +### Stacktrace recovery machinery + +This section explains the inner mechanism of stacktrace recovery and can be skipped. + +When an exception is rethrown between coroutines (e.g. through `withContext` or `Deferred.await` boundary), stacktrace recovery +machinery tries to create a copy of the original exception (with the original exception as the cause), then rewrite stacktrace +of the copy with coroutine-related stack frames (using [Throwable.setStackTrace](https://docs.oracle.com/javase/9/docs/api/java/lang/Throwable.html#setStackTrace-java.lang.StackTraceElement:A-)) +and then throws the resulting exception instead of the original one. + +Exception copy logic is straightforward: + 1) If the exception class implements [CopyableThrowable], [CopyableThrowable.createCopy] is used. + 2) If the exception class has class-specific fields not inherited from Throwable, the exception is not copied. + 3) Otherwise, one of the public exception's constructor is invoked reflectively with an optional `initCause` call. + +## Debug agent + +[kotlinx-coroutines-debug](../kotlinx-coroutines-debug) module provides one of the most powerful debug capabilities in `kotlinx.coroutines`. + +This is a separate module with a JVM agent that keeps track of all alive coroutines, introspects and dumps them similar to thread dump command, +additionally enhancing stacktraces with information where coroutine was created. + +The full tutorial of how to use debug agent can be found in the corresponding [readme](../kotlinx-coroutines-debug/README.md). + +### Debug agent and Android + +Unfortunately, Android runtime does not support Instrument API necessary for `kotlinx-coroutines-debug` to function, triggering `java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;`. + +Nevertheless, it will be possible to support debug agent on Android as soon as [GradleAspectJ-Android](https://github.com/Archinamon/android-gradle-aspectj) will support androin-gradle 3.3 + + + + + +[DEBUG_PROPERTY_NAME]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-d-e-b-u-g_-p-r-o-p-e-r-t-y_-n-a-m-e.html +[CoroutineName]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-name/index.html +[CopyableThrowable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-copyable-throwable/index.html +[CopyableThrowable.createCopy]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-copyable-throwable/create-copy.html + + diff --git a/docs/exception-handling.md b/docs/exception-handling.md index ac07e7e180..4c85d9d17c 100644 --- a/docs/exception-handling.md +++ b/docs/exception-handling.md @@ -6,8 +6,8 @@ // This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit. package kotlinx.coroutines.guide.$$1$$2 --> - - + - - + - - + [launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html @@ -66,6 +95,7 @@ helper function. [NonCancellable] job object is provided to suppress cancellatio [CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html [async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html [Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html +[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html [CoroutineDispatcher]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html [Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html [Dispatchers.Unconfined]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html @@ -106,4 +136,5 @@ helper function. [NonCancellable] job object is provided to suppress cancellatio [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/build.gradle b/kotlinx-coroutines-core/build.gradle new file mode 100644 index 0000000000..e963466ceb --- /dev/null +++ b/kotlinx-coroutines-core/build.gradle @@ -0,0 +1,108 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +apply plugin: 'kotlin-multiplatform' +apply from: rootProject.file("gradle/targets.gradle") +apply from: rootProject.file("gradle/compile-jvm-multiplatform.gradle") +apply from: rootProject.file("gradle/compile-common.gradle") +apply from: rootProject.file("gradle/compile-js-multiplatform.gradle") +apply from: rootProject.file("gradle/compile-native-multiplatform.gradle") +apply from: rootProject.file('gradle/publish-npm-js.gradle') + +/* + * All platform plugins and configuration magic happens here instead of build.gradle + * because JMV-only projects depend on core, thus core should always be initialized before configuration. + */ +kotlin { + configure(sourceSets) { + def srcDir = name.endsWith('Main') ? 'src' : 'test' + def platform = name[0..-5] + kotlin.srcDir "$platform/$srcDir" + if (name == "jvmMain") { + resources.srcDirs = ["$platform/resources"] + } else if (name == "jvmTest") { + resources.srcDirs = ["$platform/test-resources"] + } + languageSettings { + progressiveMode = true + experimentalAnnotations.each { useExperimentalAnnotation(it) } + } + } + + configure(targets) { + def targetName = it.name + compilations.all { compilation -> + def compileTask = tasks.getByName(compilation.compileKotlinTaskName) + // binary compatibility support + if (targetName.contains("jvm") && compilation.compilationName == "main") { + compileTask.kotlinOptions.freeCompilerArgs += ["-Xdump-declarations-to=${buildDir}/visibilities.json"] + } + } + } +} + +kotlin.sourceSets { + jvmTest.dependencies { + api "com.devexperts.lincheck:lincheck:$lincheck_version" + api "com.esotericsoftware:kryo:4.0.0" + } +} + +task checkJdk16() { + // only fail w/o JDK_16 when actually trying to compile, not during project setup phase + doLast { + if (!System.env.JDK_16) { + throw new GradleException("JDK_16 environment variable is not defined. " + + "Can't build against JDK 1.6 runtime and run JDK 1.6 compatibility tests. " + + "Please ensure JDK 1.6 is installed and that JDK_16 points to it.") + } + } +} + +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { + kotlinOptions.jdkHome = System.env.JDK_16 + // only fail when actually trying to compile, not during project setup phase + dependsOn(checkJdk16) +} + +jvmTest { + minHeapSize = '1g' + maxHeapSize = '1g' + enableAssertions = true + systemProperty 'java.security.manager', 'kotlinx.coroutines.TestSecurityManager' + exclude '**/*LFTest.*' + systemProperty 'kotlinx.coroutines.scheduler.keep.alive.sec', '100000' // any unpark problem hangs test +} + +task lockFreedomTest(type: Test, dependsOn: compileTestKotlinJvm) { + classpath = files { jvmTest.classpath } + testClassesDirs = files { jvmTest.testClassesDirs } + include '**/*LFTest.*' +} + +task jdk16Test(type: Test, dependsOn: [compileTestKotlinJvm, checkJdk16]) { + classpath = files { jvmTest.classpath } + testClassesDirs = files { jvmTest.testClassesDirs } + executable = "$System.env.JDK_16/bin/java" + exclude '**/*LinearizabilityTest*.*' + exclude '**/*LFTest.*' + exclude '**/exceptions/**' + exclude '**/ExceptionsGuideTest.*' +} + +// Run these tests only during nightly stress test +jdk16Test.onlyIf { project.properties['stressTest'] != null } + +// Always run those tests +task moreTest(dependsOn: [lockFreedomTest, jdk16Test]) +build.dependsOn moreTest + +task testsJar(type: Jar, dependsOn: jvmTestClasses) { + classifier = 'tests' + from compileTestKotlinJvm.destinationDir +} + +artifacts { + archives testsJar +} diff --git a/core/kotlinx-coroutines-core/README.md b/kotlinx-coroutines-core/common/README.md similarity index 100% rename from core/kotlinx-coroutines-core/README.md rename to kotlinx-coroutines-core/common/README.md diff --git a/common/kotlinx-coroutines-core-common/src/AbstractCoroutine.kt b/kotlinx-coroutines-core/common/src/AbstractCoroutine.kt similarity index 76% rename from common/kotlinx-coroutines-core-common/src/AbstractCoroutine.kt rename to kotlinx-coroutines-core/common/src/AbstractCoroutine.kt index 7d0e555e11..842187c1fd 100644 --- a/common/kotlinx-coroutines-core-common/src/AbstractCoroutine.kt +++ b/kotlinx-coroutines-core/common/src/AbstractCoroutine.kt @@ -20,11 +20,10 @@ import kotlin.jvm.* * * The following methods are available for override: * - * * [onStart] is invoked when coroutine is create in not active state and is [started][Job.start]. - * * [onCancellation] is invoked as soon as coroutine is _failing_, or is cancelled, - * or when it completes for any reason. + * * [onStart] is invoked when coroutine was created in not active state and is being [started][Job.start]. + * * [onCancelling] is invoked as soon as coroutine is being cancelled for any reason (or completes). * * [onCompleted] is invoked when coroutine completes with a value. - * * [onCompletedExceptionally] in invoked when coroutines completes with exception. + * * [onCancelled] in invoked when coroutines completes with exception (cancelled). * * @param parentContext context of the parent coroutine. * @param active when `true` (by default) coroutine is created in _active_ state, when `false` in _new_ state. @@ -37,7 +36,6 @@ public abstract class AbstractCoroutine( /** * Context of the parent coroutine. */ - @JvmField protected val parentContext: CoroutineContext, active: Boolean = true @@ -53,7 +51,7 @@ public abstract class AbstractCoroutine( */ public override val coroutineContext: CoroutineContext get() = context - override val isActive: Boolean get() = super.isActive + override val isActive: Boolean get() = super.isActive /** * Initializes parent job from the `parentContext` of this coroutine that was passed to it during construction. @@ -78,32 +76,28 @@ public abstract class AbstractCoroutine( } /** - * This function is invoked once when this coroutine is cancelled - * similarly to [invokeOnCompletion] with `onCancelling` set to `true`. - * - * The meaning of [cause] parameter: - * * Cause is `null` when job has completed normally. - * * Cause is an instance of [CancellationException] when job was cancelled _normally_. - * **It should not be treated as an error**. In particular, it should not be reported to error logs. - * * Otherwise, the job had been cancelled or failed with exception. - */ - protected override fun onCancellation(cause: Throwable?) {} - - /** - * This function is invoked once when job is completed normally with the specified [value]. + * This function is invoked once when job was completed normally with the specified [value], + * right before all the waiters for coroutine's completion are notified. */ protected open fun onCompleted(value: T) {} /** - * This function is invoked once when job is completed exceptionally with the specified [exception]. + * This function is invoked once when job was cancelled with the specified [cause], + * right before all the waiters for coroutine's completion are notified. + * + * **Note:** the state of the coroutine might not be final yet in this function and should not be queried. + * You can use [completionCause] and [completionCauseHandled] to recover parameters that we passed + * to this `onCancelled` invocation only when [isCompleted] returns `true`. + * + * @param cause The cancellation (failure) cause + * @param handled `true` if the exception was handled by parent (always `true` when it is a [CancellationException]) */ - // todo: rename to onCancelled - protected open fun onCompletedExceptionally(exception: Throwable) {} + protected open fun onCancelled(cause: Throwable, handled: Boolean) {} @Suppress("UNCHECKED_CAST") - internal override fun onCompletionInternal(state: Any?, mode: Int, suppressed: Boolean) { + protected final override fun onCompletionInternal(state: Any?) { if (state is CompletedExceptionally) - onCompletedExceptionally(state.cause) + onCancelled(state.cause, state.handled) else onCompleted(state as T) } @@ -118,7 +112,7 @@ public abstract class AbstractCoroutine( } internal final override fun handleOnCompletionException(exception: Throwable) { - handleCoroutineException(parentContext, exception, this) + handleCoroutineException(context, exception) } internal override fun nameString(): String { diff --git a/common/kotlinx-coroutines-core-common/src/Annotations.kt b/kotlinx-coroutines-core/common/src/Annotations.kt similarity index 95% rename from common/kotlinx-coroutines-core-common/src/Annotations.kt rename to kotlinx-coroutines-core/common/src/Annotations.kt index 8d7b7c53e4..2b81146d29 100644 --- a/common/kotlinx-coroutines-core-common/src/Annotations.kt +++ b/kotlinx-coroutines-core/common/src/Annotations.kt @@ -30,8 +30,6 @@ public annotation class ObsoleteCoroutinesApi * Marks declarations that are **internal** in coroutines API, which means that should not be used outside of * `kotlinx.coroutines`, because their signatures and semantics will be changing between release without any * warnings and without providing any migration aids. - * - * @suppress **This an internal API and should not be used from general code.** */ @Retention(value = AnnotationRetention.BINARY) @Experimental(level = Experimental.Level.ERROR) diff --git a/common/kotlinx-coroutines-core-common/src/Await.kt b/kotlinx-coroutines-core/common/src/Await.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/Await.kt rename to kotlinx-coroutines-core/common/src/Await.kt diff --git a/common/kotlinx-coroutines-core-common/src/Builders.common.kt b/kotlinx-coroutines-core/common/src/Builders.common.kt similarity index 85% rename from common/kotlinx-coroutines-core-common/src/Builders.common.kt rename to kotlinx-coroutines-core/common/src/Builders.common.kt index 737d0b2135..5df1e9b8db 100644 --- a/common/kotlinx-coroutines-core-common/src/Builders.common.kt +++ b/kotlinx-coroutines-core/common/src/Builders.common.kt @@ -94,7 +94,6 @@ private open class DeferredCoroutine( parentContext: CoroutineContext, active: Boolean ) : AbstractCoroutine(parentContext, active), Deferred, SelectClause1 { - override val cancelsParent: Boolean get() = true override fun getCompleted(): T = getCompletedInternal() as T override suspend fun await(): T = awaitInternal() as T override val onAwait: SelectClause1 get() = this @@ -121,9 +120,17 @@ private class LazyDeferredCoroutine( * Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns * the result. * - * This function immediately applies dispatcher from the new context, shifting execution of the block into the - * different thread inside the block, and back when it completes. - * The specified [context] is added onto the current coroutine context for the execution of the block. + * The resulting context for the [block] is derived by merging the current [coroutineContext] with the + * specified [context] using `coroutineContext + context` (see [CoroutineContext.plus]). + * This suspending function is cancellable. It immediately checks for cancellation of + * the resulting context and throws [CancellationException] if it is not [active][CoroutineContext.isActive]. + * + * This function uses dispatcher from the new context, shifting execution of the [block] into the + * different thread if a new dispatcher is specified, and back to the original dispatcher + * when it completes. Note, that the result of `withContext` invocation is + * dispatched into the original context in a cancellable way, which means that if the original [coroutineContext], + * in which `withContext` was invoked, is cancelled by the time its dispatcher starts to execute the code, + * it discards the result of `withContext` and throws [CancellationException]. */ public suspend fun withContext( context: CoroutineContext, @@ -132,6 +139,8 @@ public suspend fun withContext( // compute new context val oldContext = uCont.context val newContext = oldContext + context + // always check for cancellation of new context + newContext.checkCompletion() // FAST PATH #1 -- new context is the same as the old one if (newContext === oldContext) { val coroutine = ScopeCoroutine(newContext, uCont) // MODE_DIRECT @@ -153,14 +162,27 @@ public suspend fun withContext( coroutine.getResult() } +/** + * Calls the specified suspending block with the given [CoroutineDispatcher], suspends until it + * completes, and returns the result. + * + * This inline function calls [withContext]. + */ +@ExperimentalCoroutinesApi +public suspend inline operator fun CoroutineDispatcher.invoke( + noinline block: suspend CoroutineScope.() -> T +): T = withContext(this, block) + // --------------- implementation --------------- private open class StandaloneCoroutine( parentContext: CoroutineContext, active: Boolean ) : AbstractCoroutine(parentContext, active) { - override val cancelsParent: Boolean get() = true - override fun handleJobException(exception: Throwable) = handleExceptionViaHandler(parentContext, exception) + override fun handleJobException(exception: Throwable): Boolean { + handleCoroutineException(context, exception) + return true + } } private class LazyStandaloneCoroutine( @@ -219,10 +241,10 @@ private class DispatchedCoroutine( } } - override fun onCompletionInternal(state: Any?, mode: Int, suppressed: Boolean) { + override fun afterCompletionInternal(state: Any?, mode: Int) { if (tryResume()) return // completed before getResult invocation -- bail out // otherwise, getResult has already commenced, i.e. completed later or in other thread - super.onCompletionInternal(state, mode, suppressed) + super.afterCompletionInternal(state, mode) } fun getResult(): Any? { diff --git a/common/kotlinx-coroutines-core-common/src/CancellableContinuation.kt b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/CancellableContinuation.kt rename to kotlinx-coroutines-core/common/src/CancellableContinuation.kt diff --git a/common/kotlinx-coroutines-core-common/src/CancellableContinuationImpl.kt b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt similarity index 99% rename from common/kotlinx-coroutines-core-common/src/CancellableContinuationImpl.kt rename to kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt index 2fe1cf9e5a..813276f8ed 100644 --- a/common/kotlinx-coroutines-core-common/src/CancellableContinuationImpl.kt +++ b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt @@ -121,6 +121,7 @@ internal open class CancellableContinuationImpl( try { block() } catch (ex: Throwable) { + // Handler should never fail, if it does -- it is an unhandled exception handleCoroutineException( context, CompletionHandlerException("Exception in cancellation handler for $this", ex) diff --git a/common/kotlinx-coroutines-core-common/src/CompletableDeferred.kt b/kotlinx-coroutines-core/common/src/CompletableDeferred.kt similarity index 81% rename from common/kotlinx-coroutines-core-common/src/CompletableDeferred.kt rename to kotlinx-coroutines-core/common/src/CompletableDeferred.kt index b7f821ba6d..d8f9d81607 100644 --- a/common/kotlinx-coroutines-core-common/src/CompletableDeferred.kt +++ b/kotlinx-coroutines-core/common/src/CompletableDeferred.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. */ @file:Suppress("DEPRECATION_ERROR") @@ -25,6 +25,10 @@ public interface CompletableDeferred : Deferred { * completed as a result of this invocation and `false` otherwise (if it was already completed). * * Repeated invocations of this function have no effect and always produce `false`. + * + * This function transitions this deferred into _completed_ state if it was not completed or cancelled yet. + * However, if this deferred has children, then it transitions into _completing_ state and becomes _complete_ + * once all its children are [complete][isCompleted]. See [Job] for details. */ public fun complete(value: T): Boolean @@ -33,6 +37,10 @@ public interface CompletableDeferred : Deferred { * completed as a result of this invocation and `false` otherwise (if it was already completed). * * Repeated invocations of this function have no effect and always produce `false`. + * + * This function transitions this deferred into _cancelled_ state if it was not completed or cancelled yet. + * However, that if this deferred has children, then it transitions into _cancelling_ state and becomes _cancelled_ + * once all its children are [complete][isCompleted]. See [Job] for details. */ public fun completeExceptionally(exception: Throwable): Boolean } @@ -58,7 +66,6 @@ private class CompletableDeferredImpl( parent: Job? ) : JobSupport(true), CompletableDeferred, SelectClause1 { init { initParentJobInternal(parent) } - override val cancelsParent: Boolean get() = true override val onCancelComplete get() = true override fun getCompleted(): T = getCompletedInternal() as T override suspend fun await(): T = awaitInternal() as T diff --git a/kotlinx-coroutines-core/common/src/CompletableJob.kt b/kotlinx-coroutines-core/common/src/CompletableJob.kt new file mode 100644 index 0000000000..7f959910ce --- /dev/null +++ b/kotlinx-coroutines-core/common/src/CompletableJob.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +/** + * A job that can be completed using [complete()] function. + * It is returned by [Job()][Job] and [SupervisorJob()][SupervisorJob] constructor functions. + */ +public interface CompletableJob : Job { + /** + * Completes this job. The result is `true` if this job was completed as a result of this invocation and + * `false` otherwise (if it was already completed). + * + * Repeated invocations of this function have no effect and always produce `false`. + * + * This function transitions this job into _completed- state if it was not completed or cancelled yet. + * However, that if this job has children, then it transitions into _completing_ state and becomes _complete_ + * once all its children are [complete][isCompleted]. See [Job] for details. + */ + public fun complete(): Boolean + + /** + * Completes this job exceptionally with a given [exception]. The result is `true` if this job was + * completed as a result of this invocation and `false` otherwise (if it was already completed). + * + * Repeated invocations of this function have no effect and always produce `false`. + * + * This function transitions this job into _cancelled_ state if it was not completed or cancelled yet. + * However, that if this job has children, then it transitions into _cancelling_ state and becomes _cancelled_ + * once all its children are [complete][isCompleted]. See [Job] for details. + */ + public fun completeExceptionally(exception: Throwable): Boolean +} \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/CompletedExceptionally.kt b/kotlinx-coroutines-core/common/src/CompletedExceptionally.kt similarity index 75% rename from common/kotlinx-coroutines-core-common/src/CompletedExceptionally.kt rename to kotlinx-coroutines-core/common/src/CompletedExceptionally.kt index 0f5ad7f570..d15c857566 100644 --- a/common/kotlinx-coroutines-core-common/src/CompletedExceptionally.kt +++ b/kotlinx-coroutines-core/common/src/CompletedExceptionally.kt @@ -18,8 +18,12 @@ internal fun Result.toState(): Any? = * or artificial [CancellationException] if no cause was provided */ internal open class CompletedExceptionally( - @JvmField public val cause: Throwable + @JvmField public val cause: Throwable, + handled: Boolean = false ) { + private val _handled = atomic(handled) + val handled: Boolean get() = _handled.value + fun makeHandled(): Boolean = _handled.compareAndSet(false, true) override fun toString(): String = "$classSimpleName[$cause]" } @@ -34,10 +38,7 @@ internal class CancelledContinuation( continuation: Continuation<*>, cause: Throwable?, handled: Boolean -) : CompletedExceptionally(cause ?: CancellationException("Continuation $continuation was cancelled normally")) { - private val resumed = atomic(false) - private val handled = atomic(handled) - - fun makeResumed(): Boolean = resumed.compareAndSet(false, true) - fun makeHandled(): Boolean = handled.compareAndSet(false, true) +) : CompletedExceptionally(cause ?: CancellationException("Continuation $continuation was cancelled normally"), handled) { + private val _resumed = atomic(false) + fun makeResumed(): Boolean = _resumed.compareAndSet(false, true) } diff --git a/common/kotlinx-coroutines-core-common/src/CompletionHandler.common.kt b/kotlinx-coroutines-core/common/src/CompletionHandler.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/CompletionHandler.common.kt rename to kotlinx-coroutines-core/common/src/CompletionHandler.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/CoroutineContext.common.kt rename to kotlinx-coroutines-core/common/src/CoroutineContext.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/CoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt similarity index 87% rename from common/kotlinx-coroutines-core-common/src/CoroutineDispatcher.kt rename to kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt index c070edb78e..19308c218c 100644 --- a/common/kotlinx-coroutines-core-common/src/CoroutineDispatcher.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt @@ -33,11 +33,14 @@ public abstract class CoroutineDispatcher : * Returns `true` if execution shall be dispatched onto another thread. * The default behaviour for most dispatchers is to return `true`. * + * This method should never be used from general code, it is used only by `kotlinx.coroutines` + * internals and its contract with the rest of API is an implementation detail. + * * UI dispatchers _should not_ override `isDispatchNeeded`, but leave a default implementation that * returns `true`. To understand the rationale beyond this recommendation, consider the following code: * * ```kotlin - * fun asyncUpdateUI() = async(MainThread) { + * fun asyncUpdateUI() = async(Dispatchers.Main) { * // do something here that updates something in UI * } * ``` @@ -60,6 +63,9 @@ public abstract class CoroutineDispatcher : * parameter that allows one to optionally choose C#-style [CoroutineStart.UNDISPATCHED] behaviour * whenever it is needed for efficiency. * + * This method should be generally exception-safe, an exception thrown from this method + * may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state. + * * **Note: This is an experimental api.** Execution semantics of coroutines may change in the future when this function returns `false`. */ @ExperimentalCoroutinesApi @@ -67,6 +73,9 @@ public abstract class CoroutineDispatcher : /** * Dispatches execution of a runnable [block] onto another thread in the given [context]. + * + * This method should be generally exception-safe, an exception thrown from this method + * may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state. */ public abstract fun dispatch(context: CoroutineContext, block: Runnable) @@ -85,6 +94,9 @@ public abstract class CoroutineDispatcher : /** * Returns continuation that wraps the original [continuation], thus intercepting all resumptions. + * + * This method should be generally exception-safe, an exception thrown from this method + * may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state. */ public final override fun interceptContinuation(continuation: Continuation): Continuation = DispatchedContinuation(this, continuation) diff --git a/common/kotlinx-coroutines-core-common/src/CoroutineExceptionHandler.kt b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt similarity index 71% rename from common/kotlinx-coroutines-core-common/src/CoroutineExceptionHandler.kt rename to kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt index 927d16867b..bbf3e8eb0d 100644 --- a/common/kotlinx-coroutines-core-common/src/CoroutineExceptionHandler.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt @@ -9,34 +9,16 @@ import kotlin.coroutines.* internal expect fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) /** - * Helper function for coroutine builder implementations to handle uncaught exception in coroutines. + * Helper function for coroutine builder implementations to handle uncaught and unexpected exceptions in coroutines, + * that could not be otherwise handled in a normal way through structured concurrency, saving them to a future, and + * cannot be rethrown. This is a last resort handler to prevent lost exceptions. * - * It tries to handle uncaught exception in the following way: - * If current exception is [CancellationException], it's ignored: [CancellationException] is a normal way to cancel - * coroutine. - * - * If there is a [Job] in the context and it's not a [caller], then [Job.cancel] is invoked. - * If invocation returned `true`, method terminates: now [Job] is responsible for handling an exception. - * Otherwise, If there is [CoroutineExceptionHandler] in the context, it is used. If it throws an exception during handling - * or is absent, all instances of [CoroutineExceptionHandler] found via [ServiceLoader] and [Thread.uncaughtExceptionHandler] are invoked + * If there is [CoroutineExceptionHandler] in the context, then it is used. If it throws an exception during handling + * or is absent, all instances of [CoroutineExceptionHandler] found via [ServiceLoader] and + * [Thread.uncaughtExceptionHandler] are invoked. */ @InternalCoroutinesApi -public fun handleCoroutineException(context: CoroutineContext, exception: Throwable, caller: Job? = null) { - // Ignore CancellationException (they are normal ways to terminate a coroutine) - if (exception is CancellationException) return // nothing to do - // Try propagate exception to parent - val job = context[Job] - @Suppress("DEPRECATION") - if (job !== null && job !== caller && job.cancel(exception)) return // handle by parent - // otherwise -- use exception handlers - handleExceptionViaHandler(context, exception) -} - -/** - * @suppress This is an internal API and it is subject to change. - */ -@InternalCoroutinesApi -public fun handleExceptionViaHandler(context: CoroutineContext, exception: Throwable) { +public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) { // Invoke exception handler from the context if present try { context[CoroutineExceptionHandler]?.let { @@ -47,7 +29,6 @@ public fun handleExceptionViaHandler(context: CoroutineContext, exception: Throw handleCoroutineExceptionImpl(context, handlerException(exception, t)) return } - // If handler is not present in the context or exception was thrown, fallback to the global handler handleCoroutineExceptionImpl(context, exception) } diff --git a/common/kotlinx-coroutines-core-common/src/CoroutineName.kt b/kotlinx-coroutines-core/common/src/CoroutineName.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/CoroutineName.kt rename to kotlinx-coroutines-core/common/src/CoroutineName.kt diff --git a/common/kotlinx-coroutines-core-common/src/CoroutineScope.kt b/kotlinx-coroutines-core/common/src/CoroutineScope.kt similarity index 66% rename from common/kotlinx-coroutines-core-common/src/CoroutineScope.kt rename to kotlinx-coroutines-core/common/src/CoroutineScope.kt index 32560159fe..9b67e45d8e 100644 --- a/common/kotlinx-coroutines-core-common/src/CoroutineScope.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineScope.kt @@ -6,60 +6,54 @@ package kotlinx.coroutines import kotlinx.coroutines.internal.* import kotlinx.coroutines.intrinsics.* -import kotlin.coroutines.intrinsics.* import kotlin.coroutines.* +import kotlin.coroutines.intrinsics.* /** * Defines a scope for new coroutines. Every coroutine builder * is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext] * to automatically propagate both context elements and cancellation. * + * The best ways to obtain a standalone instance of the scope are [CoroutineScope()] and [MainScope()] factory functions. + * Additional context elements can be appended to the scope using [plus][CoroutineScope.plus] operator. + * + * Manual implementation of this interface is not recommended, implementation by delegation should be preferred instead. + * By convention, [context of the scope][CoroutineScope.coroutineContext] should contain an instance of a [job][Job] to enforce structured concurrency. + * * Every coroutine builder (like [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc) * and every scoping function (like [coroutineScope], [withContext], etc) provides _its own_ scope * with its own [Job] instance into the inner block of code it runs. - * By convention, they all wait for all the coroutines inside - * their block to complete before completing themselves, thus enforcing the - * discipline of **structured concurrency**. + * By convention, they all wait for all the coroutines inside their block to complete before completing themselves, + * thus enforcing the discipline of **structured concurrency**. * - * [CoroutineScope] should be implemented on entities with well-defined lifecycle that are responsible + * [CoroutineScope] should be implemented (or used as a field) on entities with a well-defined lifecycle that are responsible * for launching children coroutines. Example of such entity on Android is Activity. * Usage of this interface may look like this: * * ``` - * class MyActivity : AppCompatActivity(), CoroutineScope { - * lateinit var job: Job - * override val coroutineContext: CoroutineContext - * get() = Dispatchers.Main + job - * - * override fun onCreate(savedInstanceState: Bundle?) { - * super.onCreate(savedInstanceState) - * job = Job() - * } - * + * class MyActivity : AppCompatActivity(), CoroutineScope by MainScope() { * override fun onDestroy() { - * super.onDestroy() - * job.cancel() // Cancel job on activity destroy. After destroy all children jobs will be cancelled automatically + * cancel() // cancel is extension on CoroutineScope * } * * /* * * Note how coroutine builders are scoped: if activity is destroyed or any of the launched coroutines * * in this method throws an exception, then all nested coroutines are cancelled. * */ - * fun loadDataFromUI() = launch { // <- extension on current activity, launched in the main thread - * val ioData = async(Dispatchers.IO) { // <- extension on launch scope, launched in IO dispatcher - * // blocking I/O operation - * } - * // do something else concurrently with I/O - * val data = ioData.await() // wait for result of I/O - * draw(data) // can draw in the main thread + * fun showSomeData() = launch { // <- extension on current activity, launched in the main thread + * // ... here we can use suspending functions or coroutine builders with other dispatchers + * draw(data) // draw in the main thread * } * } - * * ``` */ public interface CoroutineScope { /** - * Context of this scope. + * The context of this scope. + * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope. + * Accessing this property in general code is not recommended for any purposes except accessing [Job] instance for advanced usages. + * + * By convention, should contain an instance of a [job][Job] to enforce structured concurrency. */ public val coroutineContext: CoroutineContext } @@ -94,7 +88,6 @@ public operator fun CoroutineScope.plus(context: CoroutineContext): CoroutineSco * `val scope = MainScope() + CoroutineName("MyActivity")`. */ @Suppress("FunctionName") -@ExperimentalCoroutinesApi // Experimental since 1.1.0, tentatively until 1.2.0 public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main) /** @@ -157,13 +150,13 @@ public object GlobalScope : CoroutineScope { * Example of the scope usages looks like this: * * ``` - * suspend fun loadDataForUI() = coroutineScope { + * suspend fun showSomeData() = coroutineScope { * - * val data = async { // <- extension on current scope - * ... load some UI data ... + * val data = async(Dispatchers.IO) { // <- extension on current scope + * ... load some UI data for the Main thread ... * } * - * withContext(UI) { + * withContext(Dispatchers.Main) { * doSomeWork() * val result = data.await() * display(result) @@ -172,9 +165,10 @@ public object GlobalScope : CoroutineScope { * ``` * * Semantics of the scope in this example: - * 1) `loadDataForUI` returns as soon as data is loaded and UI is updated. - * 2) If `doSomeWork` throws an exception, then `async` task is cancelled and `loadDataForUI` rethrows that exception. - * 3) If outer scope of `loadDataForUI` is cancelled, both started `async` and `withContext` are cancelled. + * 1) `showSomeData` returns as soon as data is loaded and displayed in the UI. + * 2) If `doSomeWork` throws an exception, then `async` task is cancelled and `showSomeData` rethrows that exception. + * 3) If outer scope of `showSomeData` is cancelled, both started `async` and `withContext` blocks are cancelled. + * 4) If `async` block fails, `withContext` will be cancelled. * * Method may throw [CancellationException] if the current job was cancelled externally * or may throw the corresponding unhandled [Throwable] if there is any unhandled exception in this scope @@ -198,14 +192,28 @@ public fun CoroutineScope(context: CoroutineContext): CoroutineScope = ContextScope(if (context[Job] != null) context else context + Job()) /** - * Cancels this scope, including its job and all its children. + * Cancels this scope, including its job and all its children with an optional cancellation [cause]. + * A cause can be used to specify an error message or to provide other details on + * a cancellation reason for debugging purposes. * Throws [IllegalStateException] if the scope does not have a job in it. - * - * This API is experimental in order to investigate possible clashes with other cancellation mechanisms. */ -@Suppress("NOTHING_TO_INLINE") -@ExperimentalCoroutinesApi // Experimental and inline since 1.1.0, tentatively until 1.2.0 -public inline fun CoroutineScope.cancel() { +public fun CoroutineScope.cancel(cause: CancellationException? = null) { val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this") - job.cancel() + job.cancel(cause) } + +/** + * Ensures that current scope is [active][CoroutineScope.isActive]. + * Throws [IllegalStateException] if the context does not have a job in it. + * + * If the job is no longer active, throws [CancellationException]. + * If the job was cancelled, thrown exception contains the original cancellation cause. + * + * This method is a drop-in replacement for the following code, but with more precise exception: + * ``` + * if (!isActive) { + * throw CancellationException() + * } + * ``` + */ +public fun CoroutineScope.ensureActive(): Unit = coroutineContext.ensureActive() diff --git a/common/kotlinx-coroutines-core-common/src/CoroutineStart.kt b/kotlinx-coroutines-core/common/src/CoroutineStart.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/CoroutineStart.kt rename to kotlinx-coroutines-core/common/src/CoroutineStart.kt diff --git a/common/kotlinx-coroutines-core-common/src/Debug.common.kt b/kotlinx-coroutines-core/common/src/Debug.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/Debug.common.kt rename to kotlinx-coroutines-core/common/src/Debug.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/Deferred.kt b/kotlinx-coroutines-core/common/src/Deferred.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/Deferred.kt rename to kotlinx-coroutines-core/common/src/Deferred.kt diff --git a/common/kotlinx-coroutines-core-common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/Delay.kt rename to kotlinx-coroutines-core/common/src/Delay.kt diff --git a/common/kotlinx-coroutines-core-common/src/Dispatched.kt b/kotlinx-coroutines-core/common/src/Dispatched.kt similarity index 80% rename from common/kotlinx-coroutines-core-common/src/Dispatched.kt rename to kotlinx-coroutines-core/common/src/Dispatched.kt index eb6e02f375..f656b22b84 100644 --- a/common/kotlinx-coroutines-core-common/src/Dispatched.kt +++ b/kotlinx-coroutines-core/common/src/Dispatched.kt @@ -51,7 +51,7 @@ private fun DispatchedTask<*>.resumeUnconfined() { } } -private inline fun runUnconfinedEventLoop( +private inline fun DispatchedTask<*>.runUnconfinedEventLoop( eventLoop: EventLoop, block: () -> Unit ) { @@ -64,10 +64,10 @@ private inline fun runUnconfinedEventLoop( } } catch (e: Throwable) { /* - * This exception doesn't happen normally, only if user either submitted throwing runnable - * or if we have a bug in implementation. Throw an exception that better explains the problem. + * This exception doesn't happen normally, only if we have a bug in implementation. + * Report it as a fatal exception. */ - throw DispatchException("Unexpected exception in unconfined event loop", e) + handleFatalException(e, null) } finally { eventLoop.decrementUseCount(unconfined = true) } @@ -216,6 +216,7 @@ internal abstract class DispatchedTask( public final override fun run() { val taskContext = this.taskContext + var exception: Throwable? = null try { val delegate = delegate as DispatchedContinuation val continuation = delegate.continuation @@ -234,11 +235,43 @@ internal abstract class DispatchedTask( } } } catch (e: Throwable) { - throw DispatchException("Unexpected exception running $this", e) + // This instead of runCatching to have nicer stacktrace and debug experience + exception = e } finally { - taskContext.afterTask() + val result = runCatching { taskContext.afterTask() } + handleFatalException(exception, result.exceptionOrNull()) } } + + /** + * Machinery that handles fatal exceptions in kotlinx.coroutines. + * There are two kinds of fatal exceptions: + * + * 1) Exceptions from kotlinx.coroutines code. Such exceptions indicate that either + * the library or the compiler has a bug that breaks internal invariants. + * They usually have specific workarounds, but require careful study of the cause and should + * be reported to the maintainers and fixed on the library's side anyway. + * + * 2) Exceptions from [ThreadContextElement.updateThreadContext] and [ThreadContextElement.restoreThreadContext]. + * While a user code can trigger such exception by providing an improper implementation of [ThreadContextElement], + * we can't ignore it because it may leave coroutine in the inconsistent state. + * If you encounter such exception, you can either disable this context element or wrap it into + * another context element that catches all exceptions and handles it in the application specific manner. + * + * Fatal exception handling can be intercepted with [CoroutineExceptionHandler] element in the context of + * a failed coroutine, but such exceptions should be reported anyway. + */ + internal fun handleFatalException(exception: Throwable?, finallyException: Throwable?) { + if (exception === null && finallyException === null) return + if (exception !== null && finallyException !== null) { + exception.addSuppressedThrowable(finallyException) + } + + val cause = exception ?: finallyException + val reason = CoroutinesInternalError("Fatal exception in coroutines machinery for $this. " + + "Please read KDoc to 'handleFatalException' method and report this incident to maintainers", cause!!) + handleCoroutineException(this.delegate.context, reason) + } } internal fun DispatchedContinuation.yieldUndispatched(): Boolean = diff --git a/common/kotlinx-coroutines-core-common/src/Dispatchers.common.kt b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt similarity index 78% rename from common/kotlinx-coroutines-core-common/src/Dispatchers.common.kt rename to kotlinx-coroutines-core/common/src/Dispatchers.common.kt index 94287c6db6..13c1a0dd4e 100644 --- a/common/kotlinx-coroutines-core-common/src/Dispatchers.common.kt +++ b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt @@ -47,15 +47,30 @@ public expect object Dispatchers { * mandating any specific threading policy. Nested coroutines launched in this dispatcher form an event-loop to avoid * stack overflows. * + * ### Event loop + * Event loop semantics is a purely internal concept and have no guarantees on the order of execution + * except that all queued coroutines will be executed on the current thread in the lexical scope of the outermost + * unconfined coroutine. + * + * For example, the following code: + * ``` + * withContext(Dispatcher.Unconfined) { + * println(1) + * withContext(Dispatcher.Unconfined) { // Nested unconfined + * println(2) + * } + * println(3) + * } + * println("Done") + * ``` + * Can print both "1 2 3" and "1 3 2", this is an implementation detail that can be changed. + * But it is guaranteed that "Done" will be printed only when both `withContext` are completed. + * * Note, that if you need your coroutine to be confined to a particular thread or a thread-pool after resumption, * but still want to execute it in the current call-frame until its first suspension, then you can use * an optional [CoroutineStart] parameter in coroutine builders like * [launch][CoroutineScope.launch] and [async][CoroutineScope.async] setting it to the * the value of [CoroutineStart.UNDISPATCHED]. - * - * **Note: This is an experimental api.** - * Semantics, order of execution, and particular implementation details of this dispatcher may change in the future. */ - @ExperimentalCoroutinesApi public val Unconfined: CoroutineDispatcher } diff --git a/common/kotlinx-coroutines-core-common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/EventLoop.common.kt rename to kotlinx-coroutines-core/common/src/EventLoop.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/Exceptions.common.kt b/kotlinx-coroutines-core/common/src/Exceptions.common.kt similarity index 69% rename from common/kotlinx-coroutines-core-common/src/Exceptions.common.kt rename to kotlinx-coroutines-core/common/src/Exceptions.common.kt index 3e9457287e..e8c2f5e1db 100644 --- a/common/kotlinx-coroutines-core-common/src/Exceptions.common.kt +++ b/kotlinx-coroutines-core/common/src/Exceptions.common.kt @@ -12,6 +12,9 @@ public expect class CompletionHandlerException(message: String, cause: Throwable public expect open class CancellationException(message: String?) : IllegalStateException +@Suppress("FunctionName") +public expect fun CancellationException(message: String?, cause: Throwable?) : CancellationException + internal expect class JobCancellationException( message: String, cause: Throwable?, @@ -20,6 +23,8 @@ internal expect class JobCancellationException( internal val job: Job } -internal expect class DispatchException(message: String, cause: Throwable) : RuntimeException +internal expect class CoroutinesInternalError(message: String, cause: Throwable) : Error -internal expect fun Throwable.addSuppressedThrowable(other: Throwable) \ No newline at end of file +internal expect fun Throwable.addSuppressedThrowable(other: Throwable) +// For use in tests +internal expect val RECOVER_STACK_TRACES: Boolean \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt similarity index 83% rename from common/kotlinx-coroutines-core-common/src/Job.kt rename to kotlinx-coroutines-core/common/src/Job.kt index 31fe6e1d55..df615156f4 100644 --- a/common/kotlinx-coroutines-core-common/src/Job.kt +++ b/kotlinx-coroutines-core/common/src/Job.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. */ @file:JvmMultifileClass @@ -154,27 +154,25 @@ public interface Job : CoroutineContext.Element { */ public fun start(): Boolean + /** - * @suppress + * Cancels this job with an optional cancellation [cause]. + * A cause can be used to specify an error message or to provide other details on + * a cancellation reason for debugging purposes. + * See [Job] documentation for full explanation of cancellation machinery. */ - @Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION") - @Deprecated(level = DeprecationLevel.HIDDEN, message = "Left here for binary compatibility") - @JvmName("cancel") - public fun cancel0(): Boolean = cancel(null) + public fun cancel(cause: CancellationException? = null) /** - * Cancels this job. - * See [Job] documentation for full explanation of cancellation machinery. + * @suppress This method implements old version of JVM ABI. Use [cancel]. */ - public fun cancel(): Unit + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") + public fun cancel() = cancel(null) /** - * @suppress + * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [cancel]. */ - @ObsoleteCoroutinesApi - @Deprecated(level = DeprecationLevel.WARNING, message = "Use CompletableDeferred.completeExceptionally(cause) or Job.cancel() instead", - replaceWith = ReplaceWith("cancel()") - ) + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") public fun cancel(cause: Throwable? = null): Boolean // ------------ parent-child ------------ @@ -348,10 +346,21 @@ public interface Job : CoroutineContext.Element { * is cancelled when its parent fails or is cancelled. All this job's children are cancelled in this case, too. * The invocation of [cancel][Job.cancel] with exception (other than [CancellationException]) on this job also cancels parent. * + * Conceptually, the resulting job works in the same way as the job created by the `launch { body }` invocation + * (see [launch]), but without any code in the body. It is active until cancelled or completed. Invocation of + * [CompletableJob.complete] or [CompletableJob.completeExceptionally] corresponds to the successful or + * failed completion of the body of the coroutine. + * * @param parent an optional parent job. */ @Suppress("FunctionName") -public fun Job(parent: Job? = null): Job = JobImpl(parent) +public fun Job(parent: Job? = null): CompletableJob = JobImpl(parent) + +/** @suppress Binary compatibility only */ +@Suppress("FunctionName") +@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") +@JvmName("Job") +public fun Job0(parent: Job? = null): Job = Job(parent) /** * A handle to an allocated object that can be disposed to make it eligible for garbage collection. @@ -468,21 +477,26 @@ public suspend fun Job.cancelAndJoin() { } /** - * @suppress + * Cancels all [children][Job.children] jobs of this coroutine using [Job.cancel] for all of them + * with an optional cancellation [cause]. + * Unlike [Job.cancel] on this job as a whole, the state of this job itself is not affected. */ -@ObsoleteCoroutinesApi -@Deprecated(level = DeprecationLevel.WARNING, message = "Use cancelChildren() without cause", replaceWith = ReplaceWith("cancelChildren()")) -public fun Job.cancelChildren(cause: Throwable? = null) { - @Suppress("DEPRECATION") +public fun Job.cancelChildren(cause: CancellationException? = null) { children.forEach { it.cancel(cause) } } /** - * Cancels all [children][Job.children] jobs of this coroutine using [Job.cancel] for all of them. - * Unlike [Job.cancel] on this job as a whole, the state of this job itself is not affected. + * @suppress This method implements old version of JVM ABI. Use [cancel]. + */ +@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") +public fun Job.cancelChildren() = cancelChildren(null) + +/** + * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [Job.cancelChildren]. */ -public fun Job.cancelChildren() { - children.forEach { it.cancel() } +@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") +public fun Job.cancelChildren(cause: Throwable? = null) { + children.forEach { (it as? JobSupport)?.cancelInternal(cause) } } // -------------------- CoroutineContext extensions -------------------- @@ -506,47 +520,84 @@ public fun Job.cancelChildren() { public val CoroutineContext.isActive: Boolean get() = this[Job]?.isActive == true +/** + * Cancels [Job] of this context with an optional cancellation cause. + * See [Job.cancel] for details. + */ +public fun CoroutineContext.cancel(cause: CancellationException? = null) { + this[Job]?.cancel(cause) +} /** - * @suppress + * @suppress This method implements old version of JVM ABI. Use [CoroutineContext.cancel]. */ -@Suppress("unused") -@JvmName("cancel") -@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) -public fun CoroutineContext.cancel0(): Boolean { - this[Job]?.cancel() - return true +@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") +public fun CoroutineContext.cancel() = cancel(null) + +/** + * Ensures that current job is [active][Job.isActive]. + * If the job is no longer active, throws [CancellationException]. + * If the job was cancelled, thrown exception contains the original cancellation cause. + * + * This method is a drop-in replacement for the following code, but with more precise exception: + * ``` + * if (!job.isActive) { + * throw CancellationException() + * } + * ``` + */ +public fun Job.ensureActive(): Unit { + if (!isActive) throw getCancellationException() } /** - * Cancels [Job] of this context. See [Job.cancel] for details. + * Ensures that job in the current context is [active][Job.isActive]. + * Throws [IllegalStateException] if the context does not have a job in it. + * + * If the job is no longer active, throws [CancellationException]. + * If the job was cancelled, thrown exception contains the original cancellation cause. + * + * This method is a drop-in replacement for the following code, but with more precise exception: + * ``` + * if (!isActive) { + * throw CancellationException() + * } + * ``` */ -public fun CoroutineContext.cancel(): Unit { - this[Job]?.cancel() +public fun CoroutineContext.ensureActive(): Unit { + val job = get(Job) ?: error("Context cannot be checked for liveness because it does not have a job: $this") + job.ensureActive() } /** - * @suppress + * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [CoroutineContext.cancel]. */ -@ObsoleteCoroutinesApi -@Deprecated(level = DeprecationLevel.WARNING, message = "Use cancel() without cause", replaceWith = ReplaceWith("cancel()")) +@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") public fun CoroutineContext.cancel(cause: Throwable? = null): Boolean = @Suppress("DEPRECATION") - this[Job]?.cancel(cause) ?: false + (this[Job] as? JobSupport)?.cancelInternal(cause) ?: false /** - * Cancels all children of the [Job] in this context, without touching the the state of this job itself. + * Cancels all children of the [Job] in this context, without touching the the state of this job itself + * with an optional cancellation cause. See [Job.cancel]. * It does not do anything if there is no job in the context or it has no children. */ -public fun CoroutineContext.cancelChildren() { - this[Job]?.children?.forEach { it.cancel() } +public fun CoroutineContext.cancelChildren(cause: CancellationException? = null) { + this[Job]?.children?.forEach { it.cancel(cause) } } -@ObsoleteCoroutinesApi -@Deprecated(level = DeprecationLevel.WARNING, message = "Use cancelChildren() without cause", replaceWith = ReplaceWith("cancelChildren()")) +/** + * @suppress This method implements old version of JVM ABI. Use [CoroutineContext.cancelChildren]. + */ +@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") +public fun CoroutineContext.cancelChildren() = cancelChildren(null) + +/** + * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [CoroutineContext.cancelChildren]. + */ +@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") public fun CoroutineContext.cancelChildren(cause: Throwable? = null) { - @Suppress("DEPRECATION") - this[Job]?.children?.forEach { it.cancel(cause) } + this[Job]?.children?.forEach { (it as? JobSupport)?.cancelInternal(cause) } } /** diff --git a/common/kotlinx-coroutines-core-common/src/JobSupport.kt b/kotlinx-coroutines-core/common/src/JobSupport.kt similarity index 90% rename from common/kotlinx-coroutines-core-common/src/JobSupport.kt rename to kotlinx-coroutines-core/common/src/JobSupport.kt index 8504f84c6b..4c36202ca4 100644 --- a/common/kotlinx-coroutines-core-common/src/JobSupport.kt +++ b/kotlinx-coroutines-core/common/src/JobSupport.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. */ @file:Suppress("DEPRECATION_ERROR") @@ -98,9 +98,9 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren ~ active coroutine is working (or scheduled to execution) >> childCancelled / cancelImpl invoked ## CANCELLING: state is Finishing, state.rootCause != null - ------ cancelling listeners are not admitted anymore, invokeOnCompletion(onCancellation=true) returns NonDisposableHandle + ------ cancelling listeners are not admitted anymore, invokeOnCompletion(onCancelling=true) returns NonDisposableHandle ------ new children get immediately cancelled, but are still admitted to the list - + onCancellation + + onCancelling + notifyCancelling (invoke all cancelling listeners -- cancel all children, suspended functions resume with exception) + cancelParent (rootCause of cancellation is communicated to the parent, parent is cancelled, too) ~ waits for completion of coroutine body @@ -118,7 +118,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren ------ completion listeners are not admitted anymore, invokeOnCompletion returns NonDisposableHandle + parentHandle.dispose + notifyCompletion (invoke all completion listeners) - + onCompletionInternal / onCompleted / onCompletedExceptionally + + onCompletionInternal / onCompleted / onCancelled --------------------------------------------------------------------------------- */ @@ -193,9 +193,9 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren // ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method. private fun tryFinalizeFinishingState(state: Finishing, proposedUpdate: Any?, mode: Int): Boolean { /* - * Note: proposed state can be Incompleted, e.g. + * Note: proposed state can be Incomplete, e.g. * async { - * smth.invokeOnCompletion {} // <- returns handle which implements Incomplete under the hood + * something.invokeOnCompletion {} // <- returns handle which implements Incomplete under the hood * } */ require(this.state === state) // consistency check -- it cannot change @@ -203,12 +203,12 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren require(state.isCompleting) // consistency check -- must be marked as completing val proposedException = (proposedUpdate as? CompletedExceptionally)?.cause // Create the final exception and seal the state so that no more exceptions can be added - var suppressed = false + var wasCancelling = false // KLUDGE: we cannot have contract for our own expect fun synchronized val finalException = synchronized(state) { + wasCancelling = state.isCancelling val exceptions = state.sealLocked(proposedException) val finalCause = getFinalRootCause(state, exceptions) - // Report suppressed exceptions if initial cause doesn't match final cause (due to JCE unwrapping) - if (finalCause != null) suppressed = suppressExceptions(finalCause, exceptions) || finalCause !== state.rootCause + if (finalCause != null) addSuppressedExceptions(finalCause, exceptions) finalCause } // Create the final state object @@ -220,14 +220,19 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren // cancelled job final state else -> CompletedExceptionally(finalException) } - // Now handle exception if parent can't handle it - if (finalException != null && !cancelParent(finalException)) { - handleJobException(finalException) + // Now handle the final exception + if (finalException != null) { + val handled = cancelParent(finalException) || handleJobException(finalException) + if (handled) (finalState as CompletedExceptionally).makeHandled() } + // Process state updates for the final state before the state of the Job is actually set to the final state + // to avoid races where outside observer may see the job in the final state, yet exception is not handled yet. + if (!wasCancelling) onCancelling(finalException) + onCompletionInternal(finalState) // Then CAS to completed state -> it must succeed require(_state.compareAndSet(state, finalState.boxIncomplete())) { "Unexpected state: ${_state.value}, expected: $state, update: $finalState" } // And process all post-completion actions - completeStateFinalization(state, finalState, mode, suppressed) + completeStateFinalization(state, finalState, mode) return true } @@ -242,18 +247,15 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren return exceptions.firstOrNull { it !is CancellationException } ?: exceptions[0] } - private fun suppressExceptions(rootCause: Throwable, exceptions: List): Boolean { - if (exceptions.size <= 1) return false // nothing more to do here + private fun addSuppressedExceptions(rootCause: Throwable, exceptions: List) { + if (exceptions.size <= 1) return // nothing more to do here val seenExceptions = identitySet(exceptions.size) - var suppressed = false for (exception in exceptions) { val unwrapped = unwrap(exception) if (unwrapped !== rootCause && unwrapped !is CancellationException && seenExceptions.add(unwrapped)) { rootCause.addSuppressedThrowable(unwrapped) - suppressed = true } } - return suppressed } // fast-path method to finalize normally completed coroutines without children @@ -261,12 +263,14 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren 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 if (!_state.compareAndSet(state, update.boxIncomplete())) return false - completeStateFinalization(state, update, mode, false) + onCancelling(null) // simple state is not a failure + onCompletionInternal(update) + completeStateFinalization(state, update, mode) return true } // suppressed == true when any exceptions were suppressed while building the final completion cause - private fun completeStateFinalization(state: Incomplete, update: Any?, mode: Int, suppressed: Boolean) { + private fun completeStateFinalization(state: Incomplete, update: Any?, mode: Int) { /* * Now the job in THE FINAL state. We need to properly handle the resulting state. * Order of various invocations here is important. @@ -279,14 +283,8 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren } val cause = (update as? CompletedExceptionally)?.cause /* - * 2) Invoke onCancellation: for resource cancellation resource cancellation etc. - * Only notify is was not notified yet. - * Note: we do not use notifyCancelling here, since we are going to invoke all completion as our next step - */ - if (!state.isCancelling) onCancellation(cause) - /* - * 3) Invoke completion handlers: .join(), callbacks etc. - * It's important to invoke them only AFTER exception handling, see #208 + * 2) Invoke completion handlers: .join(), callbacks etc. + * It's important to invoke them only AFTER exception handling and everything else, see #208 */ if (state is JobNode<*>) { // SINGLE/SINGLE+ state -- one completion handler (common case) try { @@ -298,16 +296,15 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren state.list?.notifyCompletion(cause) } /* - * 4) Invoke onCompletionInternal: onNext(), timeout de-registration etc. - * It should be last so all callbacks observe consistent state - * of the job which doesn't depend on callback scheduling. + * 3) Resumes the rest of the code in scoped coroutines + * (runBlocking, coroutineScope, withContext, withTimeout, etc) */ - onCompletionInternal(update, mode, suppressed) + afterCompletionInternal(update, mode) } private fun notifyCancelling(list: NodeList, cause: Throwable) { // first cancel our own children - onCancellation(cause) + onCancelling(cause) notifyHandlers>(list, cause) // then cancel parent cancelParent(cause) // tentative cancellation -- does not matter if there is no parent @@ -369,34 +366,38 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren public final override fun getCancellationException(): CancellationException { val state = this.state return when (state) { - is Finishing -> state.rootCause?.toCancellationException("Job is cancelling") + 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("Job was cancelled") - else -> JobCancellationException("Job has completed normally", null, this) + is CompletedExceptionally -> state.cause.toCancellationException() + else -> JobCancellationException("$classSimpleName has completed normally", null, this) } } - private fun Throwable.toCancellationException(message: String): CancellationException = - this as? CancellationException ?: JobCancellationException(message, this, this@JobSupport) + protected fun Throwable.toCancellationException(message: String? = null): CancellationException = + this as? CancellationException ?: + JobCancellationException(message ?: "$classSimpleName was cancelled", this, this@JobSupport) /** * Returns the cause that signals the completion of this job -- it returns the original * [cancel] cause, [CancellationException] or **`null` if this job had completed normally**. * This function throws [IllegalStateException] when invoked for an job that has not [completed][isCompleted] nor * is being cancelled yet. - * - * @suppress **This is unstable API and it is subject to change.** */ - protected fun getCompletionCause(): Throwable? = loopOnState { state -> - return when (state) { + protected val completionCause: Throwable? + get() = when (val state = state) { is Finishing -> state.rootCause ?: error("Job is still new or active: $this") is Incomplete -> error("Job is still new or active: $this") is CompletedExceptionally -> state.cause else -> null } - } + + /** + * Returns `true` when [completionCause] exception was handled by parent coroutine. + */ + protected val completionCauseHandled: Boolean + get() = state.let { it is CompletedExceptionally && it.handled } @Suppress("OverridingDeprecatedMember") public final override fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle = @@ -564,14 +565,20 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren */ internal open val onCancelComplete: Boolean get() = false - // external cancel without cause, never invoked implicitly from internal machinery - public override fun cancel() { - @Suppress("DEPRECATION") - cancel(null) // must delegate here, because some classes override cancel(x) + // external cancel with cause, never invoked implicitly from internal machinery + public override fun cancel(cause: CancellationException?) { + cancelInternal(cause) // must delegate here, because some classes override cancelInternal(x) } + // HIDDEN in Job interface. Invoked only by legacy compiled code. // external cancel with (optional) cause, never invoked implicitly from internal machinery + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Added since 1.2.0 for binary compatibility with versions <= 1.1.x") public override fun cancel(cause: Throwable?): Boolean = + cancelInternal(cause) + + // It is overridden in channel-linked implementation + // Note: Boolean result is used only in HIDDEN DEPRECATED functions that were public in versions <= 1.1.x + public open fun cancelInternal(cause: Throwable?): Boolean = cancelImpl(cause) && handlesException // Parent is cancelling child @@ -580,9 +587,17 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren } // Child was cancelled with cause + // It is overridden in supervisor implementations to ignore child cancellation public open fun childCancelled(cause: Throwable): Boolean = cancelImpl(cause) && handlesException + /** + * Makes this [Job] cancelled with a specified [cause]. + * It is used in [AbstractCoroutine]-derived classes when there is an internal failure. + */ + public fun cancelCoroutine(cause: Throwable?) = + cancelImpl(cause) + // cause is Throwable or ParentJob when cancelChild was invoked // returns true is exception was handled, false otherwise private fun cancelImpl(cause: Any?): Boolean { @@ -657,7 +672,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren val causeException = causeExceptionCache ?: createCauseException(cause).also { causeExceptionCache = it } state.addExceptionLocked(causeException) } - // take cause for notification is was not cancelling before + // take cause for notification if was not in cancelling state before state.rootCause.takeIf { !wasCancelling } } notifyRootCause?.let { notifyCancelling(state.list, it) } @@ -843,8 +858,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren } public final override val children: Sequence get() = sequence { - val state = this@JobSupport.state - when (state) { + when (val state = this@JobSupport.state) { is ChildHandleNode -> yield(state.childJob) is Incomplete -> state.list?.let { list -> list.forEach { yield(it.childJob) } @@ -869,6 +883,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren /** * Override to process any exceptions that were encountered while invoking completion handlers * installed via [invokeOnCompletion]. + * * @suppress **This is unstable API and it is subject to change.** */ internal open fun handleOnCompletionException(exception: Throwable) { @@ -876,12 +891,21 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren } /** - * This function is invoked once when job is being cancelled, fails, or is completed. - * It's an optimization for [invokeOnCompletion] with `onCancellation` set to `true`. + * This function is invoked once as soon as this job is being cancelled for any reason or completes, + * similarly to [invokeOnCompletion] with `onCancelling` set to `true`. + * + * The meaning of [cause] parameter: + * * Cause is `null` when job has completed normally. + * * Cause is an instance of [CancellationException] when job was cancelled _normally_. + * **It should not be treated as an error**. In particular, it should not be reported to error logs. + * * Otherwise, the job had been cancelled or failed with exception. + * + * The specified [cause] is not the final cancellation cause of this job. + * A job may produce other exceptions while it is failing and the final cause might be different. * * @suppress **This is unstable API and it is subject to change.* */ - protected open fun onCancellation(cause: Throwable?) {} + protected open fun onCancelling(cause: Throwable?) {} /** * When this function returns `true` the parent is cancelled on cancellation of this job. @@ -891,24 +915,30 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren * * @suppress **This is unstable API and it is subject to change.* */ - protected open val cancelsParent: Boolean get() = false + protected open val cancelsParent: Boolean get() = true /** - * Returns `true` for jobs that handle their exceptions via [handleJobException] or integrate them + * Returns `true` for jobs that handle their exceptions or integrate them * into the job's result via [onCompletionInternal]. The only instance of the [Job] that does not - * handle its exceptions is [JobImpl]. + * handle its exceptions is [JobImpl] and its subclass [SupervisorJobImpl]. * * @suppress **This is unstable API and it is subject to change.* */ protected open val handlesException: Boolean get() = true /** + * Handles the final job [exception] that was not handled by the parent coroutine. + * Returns `true` if it handles exception (so handling at later stages is not needed). + * It is designed to be overridden by launch-like coroutines + * (`StandaloneCoroutine` and `ActorCoroutine`) that don't have a result type + * that can represent exceptions. + * * This method is invoked **exactly once** when the final exception of the job is determined * and before it becomes complete. At the moment of invocation the job and all its children are complete. * * @suppress **This is unstable API and it is subject to change.* */ - protected open fun handleJobException(exception: Throwable) {} + protected open fun handleJobException(exception: Throwable): Boolean = false private fun cancelParent(cause: Throwable): Boolean { // CancellationException is considered "normal" and parent is not cancelled when child produces it. @@ -920,13 +950,24 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren } /** - * Override for post-completion actions that need to do something with the state. + * Override for completion actions that need to update some external object depending on job's state, + * right before all the waiters for coroutine's completion are notified. + * + * @param state the final state. + * + * @suppress **This is unstable API and it is subject to change.** + */ + protected open fun onCompletionInternal(state: Any?) {} + + /** + * Override for the very last action on job's completion to resume the rest of the code in scoped coroutines. + * * @param state the final state. * @param mode completion mode. - * @param suppressed true when any exceptions were suppressed while building the final completion cause. + * * @suppress **This is unstable API and it is subject to change.** */ - internal open fun onCompletionInternal(state: Any?, mode: Int, suppressed: Boolean) {} + protected open fun afterCompletionInternal(state: Any?, mode: Int) {} // for nicer debugging public override fun toString(): String = @@ -994,8 +1035,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren return } if (exception === rootCause) return // nothing to do - val eh = _exceptionsHolder // volatile read - when (eh) { + when (val eh = _exceptionsHolder) { // volatile read null -> _exceptionsHolder = exception is Throwable -> { if (exception === eh) return // nothing to do @@ -1182,11 +1222,13 @@ private class Empty(override val isActive: Boolean) : Incomplete { override fun toString(): String = "Empty{${if (isActive) "Active" else "New" }}" } -internal class JobImpl(parent: Job? = null) : JobSupport(true) { +internal open class JobImpl(parent: Job?) : JobSupport(true), CompletableJob { init { initParentJobInternal(parent) } - override val cancelsParent: Boolean get() = true override val onCancelComplete get() = true override val handlesException: Boolean get() = false + override fun complete() = makeCompleting(Unit) + override fun completeExceptionally(exception: Throwable): Boolean = + makeCompleting(CompletedExceptionally(exception)) } // -------- invokeOnCompletion nodes diff --git a/common/kotlinx-coroutines-core-common/src/MainCoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt similarity index 50% rename from common/kotlinx-coroutines-core-common/src/MainCoroutineDispatcher.kt rename to kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt index 687232d63a..3a73d239fd 100644 --- a/common/kotlinx-coroutines-core-common/src/MainCoroutineDispatcher.kt +++ b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt @@ -14,13 +14,29 @@ public abstract class MainCoroutineDispatcher : CoroutineDispatcher() { /** * Returns dispatcher that executes coroutines immediately when it is already in the right context - * (e.g. current looper is the same as this handler's looper). See [isDispatchNeeded] documentation on - * why this should not be done by default. + * (e.g. current looper is the same as this handler's looper) without an additional [re-dispatch][CoroutineDispatcher.dispatch]. + * + * Immediate dispatcher is safe from stack overflows and in case of nested invocations forms event-loop similar to [Dispatchers.Unconfined]. + * The event loop is an advanced topic and its implications can be found in [Dispatchers.Unconfined] documentation. + * + * Example of usage: + * ``` + * suspend fun updateUiElement(val text: String) { + * /* + * * If it is known that updateUiElement can be invoked both from the Main thread and from other threads, + * * `immediate` dispatcher is used as a performance optimization to avoid unnecessary dispatch. + * */ + * withContext(Dispatchers.Main.immediate) { + * uiElement.text = text + * } + * // Do context-independent logic such as logging + * } + * ``` + * * Method may throw [UnsupportedOperationException] if immediate dispatching is not supported by current dispatcher, * please refer to specific dispatcher documentation. * - * **Note: This is an experimental api.** Semantics of this dispatcher may change in the future. + * [Dispatchers.Main] supports immediate execution for Android, JavaFx and Swing platforms. */ - @ExperimentalCoroutinesApi public abstract val immediate: MainCoroutineDispatcher } diff --git a/common/kotlinx-coroutines-core-common/src/NonCancellable.kt b/kotlinx-coroutines-core/common/src/NonCancellable.kt similarity index 92% rename from common/kotlinx-coroutines-core-common/src/NonCancellable.kt rename to kotlinx-coroutines-core/common/src/NonCancellable.kt index a0dcb3ba3c..3a4faeed8e 100644 --- a/common/kotlinx-coroutines-core-common/src/NonCancellable.kt +++ b/kotlinx-coroutines-core/common/src/NonCancellable.kt @@ -92,15 +92,13 @@ public object NonCancellable : AbstractCoroutineContextElement(Job), Job { * @suppress **This an internal API and should not be used from general code.** */ @InternalCoroutinesApi - @Suppress("RETURN_TYPE_MISMATCH_ON_OVERRIDE") - override fun cancel(): Unit { - } + override fun cancel(cause: CancellationException?) {} /** * Always returns `false`. - * @suppress **This an internal API and should not be used from general code.** + * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [cancel]. */ - @InternalCoroutinesApi + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") override fun cancel(cause: Throwable?): Boolean = false // never handles exceptions /** diff --git a/common/kotlinx-coroutines-core-common/src/ResumeMode.kt b/kotlinx-coroutines-core/common/src/ResumeMode.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/ResumeMode.kt rename to kotlinx-coroutines-core/common/src/ResumeMode.kt diff --git a/common/kotlinx-coroutines-core-common/src/Runnable.common.kt b/kotlinx-coroutines-core/common/src/Runnable.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/Runnable.common.kt rename to kotlinx-coroutines-core/common/src/Runnable.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/SchedulerTask.common.kt b/kotlinx-coroutines-core/common/src/SchedulerTask.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/SchedulerTask.common.kt rename to kotlinx-coroutines-core/common/src/SchedulerTask.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/Supervisor.kt b/kotlinx-coroutines-core/common/src/Supervisor.kt similarity index 71% rename from common/kotlinx-coroutines-core-common/src/Supervisor.kt rename to kotlinx-coroutines-core/common/src/Supervisor.kt index f150737952..dd88e232f2 100644 --- a/common/kotlinx-coroutines-core-common/src/Supervisor.kt +++ b/kotlinx-coroutines-core/common/src/Supervisor.kt @@ -1,11 +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. */ @file:Suppress("DEPRECATION_ERROR") package kotlinx.coroutines +import kotlinx.coroutines.internal.* import kotlinx.coroutines.intrinsics.* import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* @@ -28,7 +29,13 @@ import kotlin.jvm.* * @param parent an optional parent job. */ @Suppress("FunctionName") -public fun SupervisorJob(parent: Job? = null) : Job = SupervisorJobImpl(parent) +public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent) + +/** @suppress Binary compatibility only */ +@Suppress("FunctionName") +@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") +@JvmName("SupervisorJob") +public fun SupervisorJob0(parent: Job? = null) : Job = SupervisorJob(parent) /** * Creates new [CoroutineScope] with [SupervisorJob] and calls the specified suspend block with this scope. @@ -46,26 +53,13 @@ public suspend fun supervisorScope(block: suspend CoroutineScope.() -> R): coroutine.startUndispatchedOrReturn(coroutine, block) } -private class SupervisorJobImpl(parent: Job?) : JobSupport(true) { - init { initParentJobInternal(parent) } - override val cancelsParent: Boolean get() = true - override val onCancelComplete get() = true - override val handlesException: Boolean get() = false +private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) { override fun childCancelled(cause: Throwable): Boolean = false } -private class SupervisorCoroutine( - parentContext: CoroutineContext, - @JvmField val uCont: Continuation -) : AbstractCoroutine(parentContext, true) { - override val defaultResumeMode: Int get() = MODE_DIRECT +private class SupervisorCoroutine( + context: CoroutineContext, + uCont: Continuation +) : ScopeCoroutine(context, uCont) { override fun childCancelled(cause: Throwable): Boolean = false - - @Suppress("UNCHECKED_CAST") - internal override fun onCompletionInternal(state: Any?, mode: Int, suppressed: Boolean) { - if (state is CompletedExceptionally) - uCont.resumeUninterceptedWithExceptionMode(state.cause, mode) - else - uCont.resumeUninterceptedMode(state as R, mode) - } } diff --git a/common/kotlinx-coroutines-core-common/src/Timeout.kt b/kotlinx-coroutines-core/common/src/Timeout.kt similarity index 93% rename from common/kotlinx-coroutines-core-common/src/Timeout.kt rename to kotlinx-coroutines-core/common/src/Timeout.kt index d84ec10559..9437fd6766 100644 --- a/common/kotlinx-coroutines-core-common/src/Timeout.kt +++ b/kotlinx-coroutines-core/common/src/Timeout.kt @@ -83,15 +83,19 @@ private open class TimeoutCoroutine( @JvmField val uCont: Continuation // unintercepted continuation ) : AbstractCoroutine(uCont.context, active = true), Runnable, Continuation, CoroutineStackFrame { override val defaultResumeMode: Int get() = MODE_DIRECT - override val callerFrame: CoroutineStackFrame? get() = (uCont as? CoroutineStackFrame)?.callerFrame - override fun getStackTraceElement(): StackTraceElement? = (uCont as? CoroutineStackFrame)?.getStackTraceElement() + override val callerFrame: CoroutineStackFrame? get() = (uCont as? CoroutineStackFrame) + override fun getStackTraceElement(): StackTraceElement? = null + + override val cancelsParent: Boolean + get() = false // it throws exception to parent instead of cancelling it + @Suppress("LeakingThis", "Deprecation") override fun run() { - cancel(TimeoutCancellationException(time, this)) + cancelCoroutine(TimeoutCancellationException(time, this)) } @Suppress("UNCHECKED_CAST") - internal override fun onCompletionInternal(state: Any?, mode: Int, suppressed: Boolean) { + override fun afterCompletionInternal(state: Any?, mode: Int) { if (state is CompletedExceptionally) uCont.resumeUninterceptedWithExceptionMode(state.cause, mode) else diff --git a/common/kotlinx-coroutines-core-common/src/Unconfined.kt b/kotlinx-coroutines-core/common/src/Unconfined.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/Unconfined.kt rename to kotlinx-coroutines-core/common/src/Unconfined.kt diff --git a/common/kotlinx-coroutines-core-common/src/Yield.kt b/kotlinx-coroutines-core/common/src/Yield.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/Yield.kt rename to kotlinx-coroutines-core/common/src/Yield.kt diff --git a/common/kotlinx-coroutines-core-common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt similarity index 97% rename from common/kotlinx-coroutines-core-common/src/channels/AbstractChannel.kt rename to kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt index d05ea29ecb..38f7c3bf16 100644 --- a/common/kotlinx-coroutines-core-common/src/channels/AbstractChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt @@ -167,7 +167,8 @@ internal abstract class AbstractSendChannel : SendChannel { // ------ SendChannel ------ public final override val isClosedForSend: Boolean get() = closedForSend != null - public final override val isFull: Boolean get() = queue.nextNode !is ReceiveOrClosed<*> && isBufferFull + public final override val isFull: Boolean get() = full + private val full: Boolean get() = queue.nextNode !is ReceiveOrClosed<*> && isBufferFull // TODO rename to `isFull` public final override suspend fun send(element: E) { // fast path -- try offer non-blocking @@ -402,7 +403,7 @@ internal abstract class AbstractSendChannel : SendChannel { private fun registerSelectSend(select: SelectInstance, element: E, block: suspend (SendChannel) -> R) { while (true) { if (select.isSelected) return - if (isFull) { + if (full) { val enqueueOp = TryEnqueueSendDesc(element, select, block) val enqueueResult = select.performAtomicIfNotSelected(enqueueOp) ?: return when { @@ -561,7 +562,8 @@ internal abstract class AbstractChannel : AbstractSendChannel(), Channel : AbstractSendChannel(), Channel : AbstractSendChannel(), Channel registerSelectReceive(select: SelectInstance, block: suspend (E) -> R) { while (true) { if (select.isSelected) return - if (isEmpty) { + if (empty) { val enqueueOp = TryEnqueueReceiveDesc(select, block as (suspend (E?) -> R), nullOnClose = false) val enqueueResult = select.performAtomicIfNotSelected(enqueueOp) ?: return when { @@ -778,7 +784,7 @@ internal abstract class AbstractChannel : AbstractSendChannel(), Channel registerSelectReceiveOrNull(select: SelectInstance, block: suspend (E?) -> R) { while (true) { if (select.isSelected) return - if (isEmpty) { + if (empty) { val enqueueOp = TryEnqueueReceiveDesc(select, block, nullOnClose = true) val enqueueResult = select.performAtomicIfNotSelected(enqueueOp) ?: return when { diff --git a/common/kotlinx-coroutines-core-common/src/channels/ArrayBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt similarity index 96% rename from common/kotlinx-coroutines-core-common/src/channels/ArrayBroadcastChannel.kt rename to kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt index 654c5b3297..7fbf0c36ea 100644 --- a/common/kotlinx-coroutines-core-common/src/channels/ArrayBroadcastChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt @@ -67,10 +67,17 @@ internal class ArrayBroadcastChannel( return true } - public override fun cancel(cause: Throwable?): Boolean = + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") + override fun cancel(cause: Throwable?): Boolean = + cancelInternal(cause) + + override fun cancel(cause: CancellationException?) { + cancelInternal(cause) + } + + private fun cancelInternal(cause: Throwable?): Boolean = close(cause).also { - @Suppress("DEPRECATION") - for (sub in subscribers) sub.cancel(cause) + for (sub in subscribers) sub.cancelInternal(cause) } // result is `OFFER_SUCCESS | OFFER_FAILED | Closed` @@ -201,7 +208,7 @@ internal class ArrayBroadcastChannel( override val isBufferAlwaysFull: Boolean get() = error("Should not be used") override val isBufferFull: Boolean get() = error("Should not be used") - override fun cancel(cause: Throwable?): Boolean = + override fun cancelInternal(cause: Throwable?): Boolean = close(cause).also { closed -> if (closed) broadcastChannel.updateHead(removeSub = this) clearBuffer() diff --git a/common/kotlinx-coroutines-core-common/src/channels/ArrayChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/channels/ArrayChannel.kt rename to kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt diff --git a/common/kotlinx-coroutines-core-common/src/channels/Broadcast.kt b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt similarity index 87% rename from common/kotlinx-coroutines-core-common/src/channels/Broadcast.kt rename to kotlinx-coroutines-core/common/src/channels/Broadcast.kt index 4e76369065..4ad6b8c42f 100644 --- a/common/kotlinx-coroutines-core-common/src/channels/Broadcast.kt +++ b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt @@ -90,23 +90,32 @@ private open class BroadcastCoroutine( protected val _channel: BroadcastChannel, active: Boolean ) : AbstractCoroutine(parentContext, active), ProducerScope, BroadcastChannel by _channel { - override val cancelsParent: Boolean get() = true override val isActive: Boolean get() = super.isActive override val channel: SendChannel get() = this - override fun cancel(cause: Throwable?): Boolean { - val wasCancelled = _channel.cancel(cause) - @Suppress("DEPRECATION") - if (wasCancelled) super.cancel(cause) // cancel the job - return wasCancelled + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") + final override fun cancel(cause: Throwable?): Boolean = + cancelInternal(cause) + + final override fun cancel(cause: CancellationException?) { + cancelInternal(cause) + } + + override fun cancelInternal(cause: Throwable?): Boolean { + _channel.cancel(cause?.toCancellationException()) // cancel the channel + cancelCoroutine(cause) // cancel the job + return true // does not matter - result is used in DEPRECATED functions only + } + + override fun onCompleted(value: Unit) { + _channel.close() } - override fun onCompletionInternal(state: Any?, mode: Int, suppressed: Boolean) { - val cause = (state as? CompletedExceptionally)?.cause + override fun onCancelled(cause: Throwable, handled: Boolean) { val processed = _channel.close(cause) - if (cause != null && !processed && suppressed) handleExceptionViaHandler(context, cause) + if (!processed && !handled) handleCoroutineException(context, cause) } } diff --git a/common/kotlinx-coroutines-core-common/src/channels/BroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt similarity index 83% rename from common/kotlinx-coroutines-core-common/src/channels/BroadcastChannel.kt rename to kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt index 6c5d8abc8e..bdb06b74d3 100644 --- a/common/kotlinx-coroutines-core-common/src/channels/BroadcastChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt @@ -30,14 +30,19 @@ public interface BroadcastChannel : SendChannel { public fun openSubscription(): ReceiveChannel /** - * Cancels reception of remaining elements from this channel. This function closes the channel with + * Cancels reception of remaining elements from this channel with an optional cause. + * This function closes the channel with * the specified cause (unless it was already closed), removes all buffered sent elements from it, * and [cancels][ReceiveChannel.cancel] all open subscriptions. - * This function returns `true` if the channel was not closed previously, or `false` otherwise. - * - * A channel that was cancelled with non-null [cause] is called a _failed_ channel. Attempts to send or - * receive on a failed channel throw the specified [cause] exception. + * A cause can be used to specify an error message or to provide other details on + * a cancellation reason for debugging purposes. */ + public fun cancel(cause: CancellationException? = null) + + /** + * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [cancel]. + */ + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Binary compatibility only") public fun cancel(cause: Throwable? = null): Boolean } diff --git a/common/kotlinx-coroutines-core-common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt similarity index 90% rename from common/kotlinx-coroutines-core-common/src/channels/Channel.kt rename to kotlinx-coroutines-core/common/src/channels/Channel.kt index 5a881410b0..286196dc7b 100644 --- a/common/kotlinx-coroutines-core-common/src/channels/Channel.kt +++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt @@ -30,14 +30,15 @@ public interface SendChannel { * Returns `true` if the channel is full (out of capacity) and the [send] attempt will suspend. * This function returns `false` for [isClosedForSend] channel. * - * **Note: This is an experimental api.** This property may change its semantics and/or name in the future. + * @suppress **Will be removed in next releases, no replacement.** */ @ExperimentalCoroutinesApi + @Deprecated(level = DeprecationLevel.ERROR, message = "Will be removed in next releases without replacement") public val isFull: Boolean /** - * Adds [element] into to this channel, suspending the caller while this channel [isFull], - * or throws exception if the channel [isClosedForSend] (see [close] for details). + * Adds [element] into to this channel, suspending the caller while the buffer of this channel is full + * or if it does not exist, or throws exception if the channel [isClosedForSend] (see [close] for details). * * Note, that closing a channel _after_ this function had suspended does not cause this suspended send invocation * to abort, because closing a channel is conceptually like sending a special "close token" over this channel. @@ -111,7 +112,7 @@ public interface SendChannel { * events.offer(event) * } * - * val uiUpdater = launch(UI, parent = UILifecycle) { + * val uiUpdater = launch(Dispatchers.Main, parent = UILifecycle) { * events.consume {} * events.cancel() * } @@ -151,13 +152,14 @@ public interface ReceiveChannel { * Returns `true` if the channel is empty (contains no elements) and the [receive] attempt will suspend. * This function returns `false` for [isClosedForReceive] channel. * - * **Note: This is an experimental api.** This property may change its semantics and/or name in the future. + * @suppress **Will be removed in next releases, no replacement.** */ @ExperimentalCoroutinesApi + @Deprecated(level = DeprecationLevel.ERROR, message = "Will be removed in next releases without replacement") public val isEmpty: Boolean /** - * Retrieves and removes the element from this channel suspending the caller while this channel [isEmpty] + * Retrieves and removes the element from this channel suspending the caller while this channel is empty, * or throws [ClosedReceiveChannelException] if the channel [isClosedForReceive]. * If the channel was closed because of the exception, it is called a _failed_ channel and this function * throws the original [close][SendChannel.close] cause exception. @@ -188,7 +190,7 @@ public interface ReceiveChannel { public val onReceive: SelectClause1 /** - * Retrieves and removes the element from this channel suspending the caller while this channel [isEmpty] + * Retrieves and removes the element from this channel suspending the caller while this channel is empty, * or returns `null` if the channel is [closed][isClosedForReceive] without cause * or throws the original [close][SendChannel.close] cause exception if the channel has _failed_. * @@ -227,7 +229,7 @@ public interface ReceiveChannel { public val onReceiveOrNull: SelectClause1 /** - * Retrieves and removes the element from this channel, or returns `null` if this channel [isEmpty] + * 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_. */ @@ -241,8 +243,10 @@ public interface ReceiveChannel { public operator fun iterator(): ChannelIterator /** - * Cancels reception of remaining elements from this channel. This function closes the channel - * and removes all buffered sent elements from it. + * Cancels reception of remaining elements from this channel with an optional [cause]. + * This function closes the channel and removes all buffered sent elements from it. + * A cause can be used to specify an error message or to provide other details on + * a cancellation reason for debugging purposes. * * Immediately after invocation of this function [isClosedForReceive] and * [isClosedForSend][SendChannel.isClosedForSend] @@ -250,21 +254,18 @@ public interface ReceiveChannel { * afterwards will throw [ClosedSendChannelException], while attempts to receive will throw * [ClosedReceiveChannelException]. */ - public fun cancel(): Unit + public fun cancel(cause: CancellationException? = null) /** - * @suppress + * @suppress This method implements old version of JVM ABI. Use [cancel]. */ - @Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION") - @Deprecated(level = DeprecationLevel.HIDDEN, message = "Left here for binary compatibility") - @JvmName("cancel") - public fun cancel0(): Boolean = cancel(null) + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") + public fun cancel() = cancel(null) /** - * @suppress + * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [cancel]. */ - @ObsoleteCoroutinesApi - @Deprecated(level = DeprecationLevel.WARNING, message = "Use cancel without cause", replaceWith = ReplaceWith("cancel()")) + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") public fun cancel(cause: Throwable? = null): Boolean } @@ -274,9 +275,8 @@ public interface ReceiveChannel { */ public interface ChannelIterator { /** - * Returns `true` if the channel has more elements suspending the caller while this channel - * [isEmpty][ReceiveChannel.isEmpty] or returns `false` if the channel - * [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause. + * Returns `true` if the channel has more elements, suspending the caller while this channel is empty, + * or returns `false` if the channel [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause. * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_. * * This function retrieves and removes the element from this channel for the subsequent invocation @@ -298,7 +298,7 @@ public interface ChannelIterator { /** * Retrieves and removes the element from this channel suspending the caller while this channel - * [isEmpty][ReceiveChannel.isEmpty] or throws [ClosedReceiveChannelException] if the channel + * is empty or throws [ClosedReceiveChannelException] if the channel * [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause. * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_. * @@ -343,7 +343,7 @@ public interface ChannelIterator { * * * When `capacity` is positive, but less than [UNLIMITED] -- it creates array-based channel with given capacity. * This channel has an array buffer of a fixed `capacity`. - * Sender suspends only when buffer is fully and receiver suspends only when buffer is empty. + * Sender suspends only when buffer is full and receiver suspends only when buffer is empty. */ public interface Channel : SendChannel, ReceiveChannel { /** diff --git a/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt b/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt new file mode 100644 index 0000000000..9382600af8 --- /dev/null +++ b/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt @@ -0,0 +1,35 @@ +/* + * 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.coroutines.* + +@Suppress("DEPRECATION") +internal open class ChannelCoroutine( + parentContext: CoroutineContext, + protected val _channel: Channel, + active: Boolean +) : AbstractCoroutine(parentContext, active), Channel by _channel { + val channel: Channel get() = this + + override fun cancel() { + cancelInternal(null) + } + + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") + final override fun cancel(cause: Throwable?): Boolean = + cancelInternal(cause) + + final override fun cancel(cause: CancellationException?) { + cancelInternal(cause) + } + + override fun cancelInternal(cause: Throwable?): Boolean { + _channel.cancel(cause?.toCancellationException()) // cancel the channel + cancelCoroutine(cause) // cancel the job + return true // does not matter - result is used in DEPRECATED functions only + } +} diff --git a/common/kotlinx-coroutines-core-common/src/channels/Channels.common.kt b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt similarity index 99% rename from common/kotlinx-coroutines-core-common/src/channels/Channels.common.kt rename to kotlinx-coroutines-core/common/src/channels/Channels.common.kt index 05cb342d06..17c19ff1d3 100644 --- a/common/kotlinx-coroutines-core-common/src/channels/Channels.common.kt +++ b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt @@ -61,9 +61,15 @@ public suspend inline fun BroadcastChannel.consumeEach(action: (E) -> Uni */ @ObsoleteCoroutinesApi public fun ReceiveChannel<*>.consumes(): CompletionHandler = { cause: Throwable? -> - @Suppress("DEPRECATION") - cancel(cause) - } + cancelConsumed(cause) +} + +@PublishedApi +internal fun ReceiveChannel<*>.cancelConsumed(cause: Throwable?) { + cancel(cause?.let { + it as? CancellationException ?: CancellationException("Channel was consumed, consumer had failed", it) + }) +} /** * Returns a [CompletionHandler] that invokes [cancel][ReceiveChannel.cancel] on all the @@ -79,8 +85,7 @@ public fun consumesAll(vararg channels: ReceiveChannel<*>): CompletionHandler = var exception: Throwable? = null for (channel in channels) try { - @Suppress("DEPRECATION") - channel.cancel(cause) + channel.cancelConsumed(cause) } catch (e: Throwable) { if (exception == null) { exception = e @@ -115,8 +120,7 @@ public inline fun ReceiveChannel.consume(block: ReceiveChannel.() - cause = e throw e } finally { - @Suppress("DEPRECATION") - cancel(cause) + cancelConsumed(cause) } } @@ -363,7 +367,7 @@ public suspend inline fun ReceiveChannel.indexOfFirst(predicate: (E) -> B * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254). */ @ObsoleteCoroutinesApi -public inline suspend fun ReceiveChannel.indexOfLast(predicate: (E) -> Boolean): Int { +public suspend inline fun ReceiveChannel.indexOfLast(predicate: (E) -> Boolean): Int { var lastIndex = -1 var index = 0 consumeEach { diff --git a/common/kotlinx-coroutines-core-common/src/channels/ConflatedBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt similarity index 93% rename from common/kotlinx-coroutines-core-common/src/channels/ConflatedBroadcastChannel.kt rename to kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt index bde65f2cd7..a4f8a852fe 100644 --- a/common/kotlinx-coroutines-core-common/src/channels/ConflatedBroadcastChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt @@ -202,10 +202,23 @@ public class ConflatedBroadcastChannel() : BroadcastChannel { } /** - * Closes this broadcast channel. Same as [close]. + * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [cancel]. */ + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") public override fun cancel(cause: Throwable?): Boolean = close(cause) + /** + * Cancels this conflated broadcast channel with an optional cause, same as [close]. + * This function closes the channel with + * the specified cause (unless it was already closed), + * and [cancels][ReceiveChannel.cancel] all open subscriptions. + * A cause can be used to specify an error message or to provide other details on + * a cancellation reason for debugging purposes. + */ + public override fun cancel(cause: CancellationException?) { + close(cause) + } + /** * Sends the value to all subscribed receives and stores this value as the most recent state for * future subscribers. This implementation never suspends. @@ -268,11 +281,10 @@ public class ConflatedBroadcastChannel() : BroadcastChannel { block.startCoroutineUnintercepted(receiver = this, completion = select.completion) } - @Suppress("DEPRECATION") private class Subscriber( private val broadcastChannel: ConflatedBroadcastChannel ) : ConflatedChannel(), ReceiveChannel { - override fun cancel(cause: Throwable?): Boolean = + override fun cancelInternal(cause: Throwable?): Boolean = close(cause).also { closed -> if (closed) broadcastChannel.closeSubscriber(this) } diff --git a/common/kotlinx-coroutines-core-common/src/channels/ConflatedChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/channels/ConflatedChannel.kt rename to kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt diff --git a/common/kotlinx-coroutines-core-common/src/channels/LinkedListChannel.kt b/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/channels/LinkedListChannel.kt rename to kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt diff --git a/common/kotlinx-coroutines-core-common/src/channels/Produce.kt b/kotlinx-coroutines-core/common/src/channels/Produce.kt similarity index 94% rename from common/kotlinx-coroutines-core-common/src/channels/Produce.kt rename to kotlinx-coroutines-core/common/src/channels/Produce.kt index 930b14f23c..9d1df1d430 100644 --- a/common/kotlinx-coroutines-core-common/src/channels/Produce.kt +++ b/kotlinx-coroutines-core/common/src/channels/Produce.kt @@ -94,9 +94,12 @@ private class ProducerCoroutine( override val isActive: Boolean get() = super.isActive - override fun onCompletionInternal(state: Any?, mode: Int, suppressed: Boolean) { - val cause = (state as? CompletedExceptionally)?.cause + override fun onCompleted(value: Unit) { + _channel.close() + } + + override fun onCancelled(cause: Throwable, handled: Boolean) { val processed = _channel.close(cause) - if (cause != null && !processed && suppressed) handleExceptionViaHandler(context, cause) + if (!processed && !handled) handleCoroutineException(context, cause) } } diff --git a/common/kotlinx-coroutines-core-common/src/channels/RendezvousChannel.kt b/kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/channels/RendezvousChannel.kt rename to kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt diff --git a/common/kotlinx-coroutines-core-common/src/internal/ArrayCopy.common.kt b/kotlinx-coroutines-core/common/src/internal/ArrayCopy.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/internal/ArrayCopy.common.kt rename to kotlinx-coroutines-core/common/src/internal/ArrayCopy.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/internal/ArrayQueue.kt b/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/internal/ArrayQueue.kt rename to kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt diff --git a/common/kotlinx-coroutines-core-common/src/internal/Atomic.kt b/kotlinx-coroutines-core/common/src/internal/Atomic.kt similarity index 95% rename from common/kotlinx-coroutines-core-common/src/internal/Atomic.kt rename to kotlinx-coroutines-core/common/src/internal/Atomic.kt index 61a7b730e1..b589db2834 100644 --- a/common/kotlinx-coroutines-core-common/src/internal/Atomic.kt +++ b/kotlinx-coroutines-core/common/src/internal/Atomic.kt @@ -26,7 +26,7 @@ private val NO_DECISION: Any = Symbol("NO_DECISION") /** * Descriptor for multi-word atomic operation. * Based on paper - * ["A Practical Multi-Word Compare-and-Swap Operation"](http://www.cl.cam.ac.uk/research/srgnetos/papers/2002-casn.pdf) + * ["A Practical Multi-Word Compare-and-Swap Operation"](https://www.cl.cam.ac.uk/research/srg/netos/papers/2002-casn.pdf) * by Timothy L. Harris, Keir Fraser and Ian A. Pratt. * * Note: parts of atomic operation must be globally ordered. Otherwise, this implementation will produce diff --git a/common/kotlinx-coroutines-core-common/src/internal/Concurrent.common.kt b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/internal/Concurrent.common.kt rename to kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/internal/LockFreeLinkedList.common.kt b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/internal/LockFreeLinkedList.common.kt rename to kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/internal/MainDispatcherFactory.kt b/kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/internal/MainDispatcherFactory.kt rename to kotlinx-coroutines-core/common/src/internal/MainDispatcherFactory.kt diff --git a/common/kotlinx-coroutines-core-common/src/internal/ProbesSupport.common.kt b/kotlinx-coroutines-core/common/src/internal/ProbesSupport.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/internal/ProbesSupport.common.kt rename to kotlinx-coroutines-core/common/src/internal/ProbesSupport.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/internal/Scopes.kt b/kotlinx-coroutines-core/common/src/internal/Scopes.kt similarity index 88% rename from common/kotlinx-coroutines-core-common/src/internal/Scopes.kt rename to kotlinx-coroutines-core/common/src/internal/Scopes.kt index 56a32bc075..e424c9a2e8 100644 --- a/common/kotlinx-coroutines-core-common/src/internal/Scopes.kt +++ b/kotlinx-coroutines-core/common/src/internal/Scopes.kt @@ -19,8 +19,11 @@ internal open class ScopeCoroutine( final override fun getStackTraceElement(): StackTraceElement? = null override val defaultResumeMode: Int get() = MODE_DIRECT + override val cancelsParent: Boolean + get() = false // it throws exception to parent instead of cancelling it + @Suppress("UNCHECKED_CAST") - internal override fun onCompletionInternal(state: Any?, mode: Int, suppressed: Boolean) { + override fun afterCompletionInternal(state: Any?, mode: Int) { if (state is CompletedExceptionally) { val exception = if (mode == MODE_IGNORE) state.cause else recoverStackTrace(state.cause, uCont) uCont.resumeUninterceptedWithExceptionMode(exception, mode) diff --git a/common/kotlinx-coroutines-core-common/src/internal/StackTraceRecovery.common.kt b/kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/internal/StackTraceRecovery.common.kt rename to kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/internal/Symbol.kt b/kotlinx-coroutines-core/common/src/internal/Symbol.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/internal/Symbol.kt rename to kotlinx-coroutines-core/common/src/internal/Symbol.kt diff --git a/common/kotlinx-coroutines-core-common/src/internal/Synchronized.common.kt b/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/internal/Synchronized.common.kt rename to kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt diff --git a/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt b/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt new file mode 100644 index 0000000000..b7e67ec0bd --- /dev/null +++ b/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:JvmName("SystemPropsKt") +@file:JvmMultifileClass + +package kotlinx.coroutines.internal + +import kotlin.jvm.* + +/** + * Gets the system property indicated by the specified [property name][propertyName], + * or returns [defaultValue] if there is no property with that key. + * + * **Note: this function should be used in JVM tests only, other platforms use the default value.** + */ +internal fun systemProp( + propertyName: String, + defaultValue: Boolean +): Boolean = systemProp(propertyName)?.toBoolean() ?: defaultValue + +/** + * Gets the system property indicated by the specified [property name][propertyName], + * or returns [defaultValue] if there is no property with that key. It also checks that the result + * is between [minValue] and [maxValue] (inclusively), throws [IllegalStateException] if it is not. + * + * **Note: this function should be used in JVM tests only, other platforms use the default value.** + */ +internal fun systemProp( + propertyName: String, + defaultValue: Int, + minValue: Int = 1, + maxValue: Int = Int.MAX_VALUE +): Int = systemProp(propertyName, defaultValue.toLong(), minValue.toLong(), maxValue.toLong()).toInt() + +/** + * Gets the system property indicated by the specified [property name][propertyName], + * or returns [defaultValue] if there is no property with that key. It also checks that the result + * is between [minValue] and [maxValue] (inclusively), throws [IllegalStateException] if it is not. + * + * **Note: this function should be used in JVM tests only, other platforms use the default value.** + */ +internal fun systemProp( + propertyName: String, + defaultValue: Long, + minValue: Long = 1, + maxValue: Long = Long.MAX_VALUE +): Long { + val value = systemProp(propertyName) ?: return defaultValue + val parsed = value.toLongOrNull() + ?: error("System property '$propertyName' has unrecognized value '$value'") + if (parsed !in minValue..maxValue) { + error("System property '$propertyName' should be in range $minValue..$maxValue, but is '$parsed'") + } + return parsed +} + +/** + * Gets the system property indicated by the specified [property name][propertyName], + * or returns `null` if there is no property with that key. + * + * **Note: this function should be used in JVM tests only, other platforms use the default value.** + */ +internal expect fun systemProp(propertyName: String): String? \ No newline at end of file diff --git a/common/kotlinx-coroutines-core-common/src/internal/ThreadContext.common.kt b/kotlinx-coroutines-core/common/src/internal/ThreadContext.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/internal/ThreadContext.common.kt rename to kotlinx-coroutines-core/common/src/internal/ThreadContext.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/internal/ThreadLocal.common.kt b/kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/internal/ThreadLocal.common.kt rename to kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt diff --git a/common/kotlinx-coroutines-core-common/src/intrinsics/Cancellable.kt b/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/intrinsics/Cancellable.kt rename to kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt diff --git a/common/kotlinx-coroutines-core-common/src/intrinsics/Undispatched.kt b/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/intrinsics/Undispatched.kt rename to kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt diff --git a/common/kotlinx-coroutines-core-common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt similarity index 96% rename from common/kotlinx-coroutines-core-common/src/selects/Select.kt rename to kotlinx-coroutines-core/common/src/selects/Select.kt index d9cce0d71a..2ec7fc6481 100644 --- a/common/kotlinx-coroutines-core-common/src/selects/Select.kt +++ b/kotlinx-coroutines-core/common/src/selects/Select.kt @@ -197,7 +197,12 @@ private val RESUMED: Any = Symbol("RESUMED") internal class SelectBuilderImpl( private val uCont: Continuation // unintercepted delegate continuation ) : LockFreeLinkedListHead(), SelectBuilder, - SelectInstance, Continuation { + SelectInstance, Continuation, CoroutineStackFrame { + override val callerFrame: CoroutineStackFrame? + get() = uCont as? CoroutineStackFrame + + override fun getStackTraceElement(): StackTraceElement? = null + // selection state is "this" (list of nodes) initially and is replaced by idempotent marker (or null) when selected private val _state = atomic(this) @@ -247,7 +252,11 @@ internal class SelectBuilderImpl( // Resumes in MODE_DIRECT override fun resumeWith(result: Result) { doResume({ result.toState() }) { - uCont.resumeWith(result) + if (result.isFailure) { + uCont.resumeWithStackTrace(result.exceptionOrNull()!!) + } else { + uCont.resumeWith(result) + } } } @@ -300,10 +309,13 @@ internal class SelectBuilderImpl( @PublishedApi internal fun handleBuilderException(e: Throwable) { - if (trySelect(null)) + if (trySelect(null)) { resumeWithException(e) - else + } else { + // Cannot handle this exception -- builder was already resumed with a different exception, + // so treat it as "unhandled exception" handleCoroutineException(context, e) + } } override val isSelected: Boolean get() = state !== this diff --git a/common/kotlinx-coroutines-core-common/src/selects/SelectUnbiased.kt b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/selects/SelectUnbiased.kt rename to kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt diff --git a/common/kotlinx-coroutines-core-common/src/selects/WhileSelect.kt b/kotlinx-coroutines-core/common/src/selects/WhileSelect.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/src/selects/WhileSelect.kt rename to kotlinx-coroutines-core/common/src/selects/WhileSelect.kt diff --git a/common/kotlinx-coroutines-core-common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt similarity index 98% rename from common/kotlinx-coroutines-core-common/src/sync/Mutex.kt rename to kotlinx-coroutines-core/common/src/sync/Mutex.kt index 0180956803..bb759925e8 100644 --- a/common/kotlinx-coroutines-core-common/src/sync/Mutex.kt +++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt @@ -100,7 +100,8 @@ public fun Mutex(locked: Boolean = false): Mutex = /** * Executes the given [action] under this mutex's lock. * - * @param owner Optional owner token for debugging. + * @param owner Optional owner token for debugging. When `owner` is specified (non-null value) and this mutex + * is already locked with the same token (same identity), this function throws [IllegalStateException]. * * @return the return value of the action. */ diff --git a/common/kotlinx-coroutines-core-common/test/AbstractCoroutineTest.kt b/kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt similarity index 83% rename from common/kotlinx-coroutines-core-common/test/AbstractCoroutineTest.kt rename to kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt index c2eddbf0e1..ffde0f9635 100644 --- a/common/kotlinx-coroutines-core-common/test/AbstractCoroutineTest.kt +++ b/kotlinx-coroutines-core/common/test/AbstractCoroutineTest.kt @@ -18,29 +18,29 @@ class AbstractCoroutineTest : TestBase() { expect(3) } - override fun onCancellation(cause: Throwable?) { + override fun onCancelling(cause: Throwable?) { assertEquals(null, cause) expect(5) } override fun onCompleted(value: String) { assertEquals("OK", value) - expect(8) + expect(6) } - override fun onCompletedExceptionally(exception: Throwable) { + override fun onCancelled(cause: Throwable, handled: Boolean) { expectUnreached() } } coroutine.invokeOnCompletion(onCancelling = true) { assertEquals(null, it) - expect(6) + expect(7) } coroutine.invokeOnCompletion { assertEquals(null, it) - expect(7) + expect(8) } expect(2) coroutine.start() @@ -58,7 +58,7 @@ class AbstractCoroutineTest : TestBase() { expect(3) } - override fun onCancellation(cause: Throwable?) { + override fun onCancelling(cause: Throwable?) { assertTrue(cause is TestException1) expect(5) } @@ -67,9 +67,9 @@ class AbstractCoroutineTest : TestBase() { expectUnreached() } - override fun onCompletedExceptionally(exception: Throwable) { - assertTrue(exception is TestException1) - expect(9) + override fun onCancelled(cause: Throwable, handled: Boolean) { + assertTrue(cause is TestException1) + expect(8) } } @@ -80,13 +80,13 @@ class AbstractCoroutineTest : TestBase() { coroutine.invokeOnCompletion { assertTrue(it is TestException1) - expect(8) + expect(9) } expect(2) coroutine.start() expect(4) - coroutine.cancel(TestException1()) + coroutine.cancelCoroutine(TestException1()) expect(7) coroutine.resumeWithException(TestException2()) finish(10) diff --git a/common/kotlinx-coroutines-core-common/test/AsyncLazyTest.kt b/kotlinx-coroutines-core/common/test/AsyncLazyTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/AsyncLazyTest.kt rename to kotlinx-coroutines-core/common/test/AsyncLazyTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/AsyncTest.kt b/kotlinx-coroutines-core/common/test/AsyncTest.kt similarity index 95% rename from common/kotlinx-coroutines-core-common/test/AsyncTest.kt rename to kotlinx-coroutines-core/common/test/AsyncTest.kt index f0c1c0d274..6fd4ebbe04 100644 --- a/common/kotlinx-coroutines-core-common/test/AsyncTest.kt +++ b/kotlinx-coroutines-core/common/test/AsyncTest.kt @@ -52,16 +52,20 @@ class AsyncTest : TestBase() { } @Test - fun testCancellationWithCause() = runTest(expected = { it is TestException }) { + fun testCancellationWithCause() = runTest { expect(1) val d = async(NonCancellable, start = CoroutineStart.ATOMIC) { - finish(3) + expect(3) yield() } - expect(2) - d.cancel(TestException()) - d.await() + d.cancel(TestCancellationException("TEST")) + try { + d.await() + } catch (e: TestCancellationException) { + finish(4) + assertEquals("TEST", e.message) + } } @Test @@ -155,7 +159,7 @@ class AsyncTest : TestBase() { throw TestException() } expect(1) - deferred.cancel(TestException()) + deferred.cancel() try { deferred.await() } catch (e: TestException) { diff --git a/common/kotlinx-coroutines-core-common/test/AtomicCancellationCommonTest.kt b/kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/AtomicCancellationCommonTest.kt rename to kotlinx-coroutines-core/common/test/AtomicCancellationCommonTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/AwaitTest.kt b/kotlinx-coroutines-core/common/test/AwaitTest.kt similarity index 97% rename from common/kotlinx-coroutines-core-common/test/AwaitTest.kt rename to kotlinx-coroutines-core/common/test/AwaitTest.kt index b86c7852f9..0949b62c8c 100644 --- a/common/kotlinx-coroutines-core-common/test/AwaitTest.kt +++ b/kotlinx-coroutines-core/common/test/AwaitTest.kt @@ -37,11 +37,11 @@ class AwaitTest : TestBase() { fun testAwaitAllLazy() = runTest { expect(1) val d = async(start = CoroutineStart.LAZY) { - expect(2); + expect(2) 1 } val d2 = async(start = CoroutineStart.LAZY) { - expect(3); + expect(3) 2 } assertEquals(listOf(1, 2), awaitAll(d, d2)) @@ -203,9 +203,9 @@ class AwaitTest : TestBase() { @Test fun testAwaitAllFullyCompletedExceptionally() = runTest { val d1 = CompletableDeferred(parent = null) - .apply { cancel(TestException()) } + .apply { completeExceptionally(TestException()) } val d2 = CompletableDeferred(parent = null) - .apply { cancel(TestException()) } + .apply { completeExceptionally(TestException()) } val job = async { expect(3) } expect(1) try { diff --git a/common/kotlinx-coroutines-core-common/test/CancellableContinuationHandlersTest.kt b/kotlinx-coroutines-core/common/test/CancellableContinuationHandlersTest.kt similarity index 84% rename from common/kotlinx-coroutines-core-common/test/CancellableContinuationHandlersTest.kt rename to kotlinx-coroutines-core/common/test/CancellableContinuationHandlersTest.kt index 80c43cfffd..00f719e632 100644 --- a/common/kotlinx-coroutines-core-common/test/CancellableContinuationHandlersTest.kt +++ b/kotlinx-coroutines-core/common/test/CancellableContinuationHandlersTest.kt @@ -2,6 +2,8 @@ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913 + package kotlinx.coroutines import kotlin.coroutines.* @@ -77,10 +79,18 @@ class CancellableContinuationHandlersTest : TestBase() { } @Test - fun testExceptionInHandler() = runTest({it is CompletionHandlerException}) { - suspendCancellableCoroutine { c -> - c.invokeOnCancellation { throw AssertionError() } - c.cancel() + fun testExceptionInHandler() = runTest( + unhandled = listOf({ it -> it is CompletionHandlerException }) + ) { + expect(1) + try { + suspendCancellableCoroutine { c -> + c.invokeOnCancellation { throw AssertionError() } + c.cancel() + } + } catch (e: CancellationException) { + expect(2) } + finish(3) } } diff --git a/common/kotlinx-coroutines-core-common/test/CancellableContinuationTest.kt b/kotlinx-coroutines-core/common/test/CancellableContinuationTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/CancellableContinuationTest.kt rename to kotlinx-coroutines-core/common/test/CancellableContinuationTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/CompletableDeferredTest.kt b/kotlinx-coroutines-core/common/test/CompletableDeferredTest.kt similarity index 96% rename from common/kotlinx-coroutines-core-common/test/CompletableDeferredTest.kt rename to kotlinx-coroutines-core/common/test/CompletableDeferredTest.kt index 967853b75c..999ff86297 100644 --- a/common/kotlinx-coroutines-core-common/test/CompletableDeferredTest.kt +++ b/kotlinx-coroutines-core/common/test/CompletableDeferredTest.kt @@ -64,9 +64,9 @@ class CompletableDeferredTest : TestBase() { @Test fun testCancelWithException() { val c = CompletableDeferred() - assertEquals(true, c.cancel(TestException())) + assertEquals(true, c.completeExceptionally(TestException())) checkCancelWithException(c) - assertEquals(false, c.cancel(TestException())) + assertEquals(false, c.completeExceptionally(TestException())) checkCancelWithException(c) } @@ -111,7 +111,7 @@ class CompletableDeferredTest : TestBase() { val c = CompletableDeferred(parent) checkFresh(c) assertEquals(true, parent.isActive) - assertEquals(true, c.cancel(TestException())) + assertEquals(true, c.completeExceptionally(TestException())) checkCancelWithException(c) assertEquals(false, parent.isActive) assertEquals(true, parent.isCancelled) diff --git a/kotlinx-coroutines-core/common/test/CompletableJobTest.kt b/kotlinx-coroutines-core/common/test/CompletableJobTest.kt new file mode 100644 index 0000000000..335e5d5672 --- /dev/null +++ b/kotlinx-coroutines-core/common/test/CompletableJobTest.kt @@ -0,0 +1,49 @@ +/* + * 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.test.* + +class CompletableJobTest { + @Test + fun testComplete() { + val job = Job() + assertTrue(job.isActive) + assertFalse(job.isCompleted) + assertTrue(job.complete()) + assertTrue(job.isCompleted) + assertFalse(job.isActive) + assertFalse(job.isCancelled) + assertFalse(job.complete()) + } + + @Test + fun testCompleteWithException() { + val job = Job() + assertTrue(job.isActive) + assertFalse(job.isCompleted) + assertTrue(job.completeExceptionally(TestException())) + assertTrue(job.isCompleted) + assertFalse(job.isActive) + assertTrue(job.isCancelled) + assertFalse(job.completeExceptionally(TestException())) + assertFalse(job.complete()) + } + + @Test + fun testCompleteWithChildren() { + val parent = Job() + val child = Job(parent) + assertTrue(parent.complete()) + assertFalse(parent.complete()) + assertTrue(parent.isActive) + assertFalse(parent.isCompleted) + assertTrue(child.complete()) + assertTrue(child.isCompleted) + assertTrue(parent.isCompleted) + assertFalse(child.isActive) + assertFalse(parent.isActive) + } +} \ No newline at end of file diff --git a/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt b/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt new file mode 100644 index 0000000000..6fdd3bbe8b --- /dev/null +++ b/kotlinx-coroutines-core/common/test/CoroutineDispatcherOperatorFunInvokeTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlin.coroutines.ContinuationInterceptor +import kotlin.coroutines.CoroutineContext +import kotlin.test.* + +class CoroutineDispatcherOperatorFunInvokeTest : TestBase() { + + /** + * Copy pasted from [WithContextTest.testThrowException], + * then edited to use operator. + */ + @Test + fun testThrowException() = runTest { + expect(1) + try { + (wrappedCurrentDispatcher()) { + expect(2) + throw AssertionError() + } + } catch (e: AssertionError) { + expect(3) + } + + yield() + finish(4) + } + + /** + * Copy pasted from [WithContextTest.testWithContextChildWaitSameContext], + * then edited to use operator fun invoke for [CoroutineDispatcher]. + */ + @Test + fun testWithContextChildWaitSameContext() = runTest { + expect(1) + (wrappedCurrentDispatcher()) { + expect(2) + launch { + // ^^^ schedules to main thread + expect(4) // waits before return + } + expect(3) + "OK".wrap() + }.unwrap() + finish(5) + } + + private class Wrapper(val value: String) : Incomplete { + override val isActive: Boolean + get() = error("") + override val list: NodeList? + get() = error("") + } + + private fun String.wrap() = Wrapper(this) + private fun Wrapper.unwrap() = value + + private fun CoroutineScope.wrappedCurrentDispatcher() = object : CoroutineDispatcher() { + val dispatcher = coroutineContext[ContinuationInterceptor] as CoroutineDispatcher + override fun dispatch(context: CoroutineContext, block: Runnable) { + dispatcher.dispatch(context, block) + } + + @ExperimentalCoroutinesApi + override fun isDispatchNeeded(context: CoroutineContext): Boolean { + return dispatcher.isDispatchNeeded(context) + } + + @InternalCoroutinesApi + override fun dispatchYield(context: CoroutineContext, block: Runnable) { + dispatcher.dispatchYield(context, block) + } + } +} diff --git a/common/kotlinx-coroutines-core-common/test/CoroutineExceptionHandlerTest.kt b/kotlinx-coroutines-core/common/test/CoroutineExceptionHandlerTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/CoroutineExceptionHandlerTest.kt rename to kotlinx-coroutines-core/common/test/CoroutineExceptionHandlerTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/CoroutineScopeTest.kt b/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt similarity index 95% rename from common/kotlinx-coroutines-core-common/test/CoroutineScopeTest.kt rename to kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt index 4339ae036f..c46f41a073 100644 --- a/common/kotlinx-coroutines-core-common/test/CoroutineScopeTest.kt +++ b/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt @@ -120,9 +120,14 @@ class CoroutineScopeTest : TestBase() { try { callJobScoped() expectUnreached() - } catch (e: CancellationException) { + } catch (e: JobCancellationException) { expect(5) - assertNull(e.cause) + if (RECOVER_STACK_TRACES) { + val cause = e.cause as JobCancellationException // shall be recovered JCE + assertNull(cause.cause) + } else { + assertNull(e.cause) + } } } repeat(3) { yield() } // let everything to start properly diff --git a/common/kotlinx-coroutines-core-common/test/CoroutinesTest.kt b/kotlinx-coroutines-core/common/test/CoroutinesTest.kt similarity index 97% rename from common/kotlinx-coroutines-core-common/test/CoroutinesTest.kt rename to kotlinx-coroutines-core/common/test/CoroutinesTest.kt index b2ca7279a6..534cfd61ce 100644 --- a/common/kotlinx-coroutines-core-common/test/CoroutinesTest.kt +++ b/kotlinx-coroutines-core/common/test/CoroutinesTest.kt @@ -313,7 +313,9 @@ class CoroutinesTest : TestBase() { } @Test - fun testNotCancellableChildWithExceptionCancelled() = runTest(expected = { it is IllegalArgumentException }) { + fun testNotCancellableChildWithExceptionCancelled() = runTest( + expected = { it is TestException } + ) { expect(1) // CoroutineStart.ATOMIC makes sure it will not get cancelled for it starts executing val d = async(NonCancellable, start = CoroutineStart.ATOMIC) { @@ -323,8 +325,8 @@ class CoroutinesTest : TestBase() { } expect(2) // now cancel with some other exception - d.cancel(IllegalArgumentException()) - // now await to see how it got crashed -- IAE should have been suppressed by TestException + d.cancel(TestCancellationException()) + // now await to see how it got crashed -- TestCancellationException should have been suppressed by TestException expect(3) d.await() } diff --git a/common/kotlinx-coroutines-core-common/test/DelayTest.kt b/kotlinx-coroutines-core/common/test/DelayTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/DelayTest.kt rename to kotlinx-coroutines-core/common/test/DelayTest.kt diff --git a/kotlinx-coroutines-core/common/test/EnsureActiveTest.kt b/kotlinx-coroutines-core/common/test/EnsureActiveTest.kt new file mode 100644 index 0000000000..716be629e8 --- /dev/null +++ b/kotlinx-coroutines-core/common/test/EnsureActiveTest.kt @@ -0,0 +1,77 @@ +/* + * 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.test.* + +class EnsureActiveTest : TestBase() { + + private val job = Job() + private val scope = CoroutineScope(job + CoroutineExceptionHandler { _, _ -> }) + + @Test + fun testIsActive() = runTest { + expect(1) + scope.launch(Dispatchers.Unconfined) { + ensureActive() + coroutineContext.ensureActive() + coroutineContext[Job]!!.ensureActive() + expect(2) + delay(Long.MAX_VALUE) + } + + expect(3) + job.ensureActive() + scope.ensureActive() + scope.coroutineContext.ensureActive() + job.cancelAndJoin() + finish(4) + } + + @Test + fun testIsCompleted() = runTest { + expect(1) + scope.launch(Dispatchers.Unconfined) { + ensureActive() + coroutineContext.ensureActive() + coroutineContext[Job]!!.ensureActive() + expect(2) + } + + expect(3) + job.complete() + job.join() + assertFailsWith { job.ensureActive() } + assertFailsWith { scope.ensureActive() } + assertFailsWith { scope.coroutineContext.ensureActive() } + finish(4) + } + + + @Test + fun testIsCancelled() = runTest { + expect(1) + scope.launch(Dispatchers.Unconfined) { + ensureActive() + coroutineContext.ensureActive() + coroutineContext[Job]!!.ensureActive() + expect(2) + throw TestException() + } + + expect(3) + checkException { job.ensureActive() } + checkException { scope.ensureActive() } + checkException { scope.coroutineContext.ensureActive() } + finish(4) + } + + private inline fun checkException(block: () -> Unit) { + val result = runCatching(block) + val exception = result.exceptionOrNull() ?: fail() + assertTrue(exception is JobCancellationException) + assertTrue(exception.cause is TestException) + } +} diff --git a/common/kotlinx-coroutines-core-common/test/ExperimentalDispatchModeTest.kt b/kotlinx-coroutines-core/common/test/ExperimentalDispatchModeTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/ExperimentalDispatchModeTest.kt rename to kotlinx-coroutines-core/common/test/ExperimentalDispatchModeTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/FailedJobTest.kt b/kotlinx-coroutines-core/common/test/FailedJobTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/FailedJobTest.kt rename to kotlinx-coroutines-core/common/test/FailedJobTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/JobStatesTest.kt b/kotlinx-coroutines-core/common/test/JobStatesTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/JobStatesTest.kt rename to kotlinx-coroutines-core/common/test/JobStatesTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/JobTest.kt b/kotlinx-coroutines-core/common/test/JobTest.kt similarity index 99% rename from common/kotlinx-coroutines-core-common/test/JobTest.kt rename to kotlinx-coroutines-core/common/test/JobTest.kt index d6fadbeb43..04d3c9e012 100644 --- a/common/kotlinx-coroutines-core-common/test/JobTest.kt +++ b/kotlinx-coroutines-core/common/test/JobTest.kt @@ -203,7 +203,7 @@ class JobTest : TestBase() { fun testJobWithParentCancelException() { val parent = Job() val job = Job(parent) - job.cancel(TestException()) + job.completeExceptionally(TestException()) assertTrue(job.isCancelled) assertTrue(parent.isCancelled) } diff --git a/common/kotlinx-coroutines-core-common/test/LaunchLazyTest.kt b/kotlinx-coroutines-core/common/test/LaunchLazyTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/LaunchLazyTest.kt rename to kotlinx-coroutines-core/common/test/LaunchLazyTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/NonCancellableTest.kt b/kotlinx-coroutines-core/common/test/NonCancellableTest.kt similarity index 77% rename from common/kotlinx-coroutines-core-common/test/NonCancellableTest.kt rename to kotlinx-coroutines-core/common/test/NonCancellableTest.kt index 25a6a4734f..07c3f9b725 100644 --- a/common/kotlinx-coroutines-core-common/test/NonCancellableTest.kt +++ b/kotlinx-coroutines-core/common/test/NonCancellableTest.kt @@ -29,8 +29,13 @@ class NonCancellableTest : TestBase() { try { job.await() expectUnreached() - } catch (e: CancellationException) { - assertNull(e.cause) + } catch (e: JobCancellationException) { + if (RECOVER_STACK_TRACES) { + val cause = e.cause as JobCancellationException // shall be recovered JCE + assertNull(cause.cause) + } else { + assertNull(e.cause) + } finish(6) } } @@ -51,13 +56,14 @@ class NonCancellableTest : TestBase() { } yield() - deferred.cancel(TestException()) + deferred.cancel(TestCancellationException("TEST")) expect(3) assertTrue(deferred.isCancelled) try { deferred.await() expectUnreached() - } catch (e: TestException) { + } catch (e: TestCancellationException) { + assertEquals("TEST", e.message) finish(6) } } @@ -118,8 +124,13 @@ class NonCancellableTest : TestBase() { try { job.await() expectUnreached() - } catch (e: CancellationException) { - assertNull(e.cause) + } catch (e: JobCancellationException) { + if (RECOVER_STACK_TRACES) { + val cause = e.cause as JobCancellationException // shall be recovered JCE + assertNull(cause.cause) + } else { + assertNull(e.cause) + } finish(7) } } diff --git a/common/kotlinx-coroutines-core-common/test/ParentCancellationTest.kt b/kotlinx-coroutines-core/common/test/ParentCancellationTest.kt similarity index 77% rename from common/kotlinx-coroutines-core-common/test/ParentCancellationTest.kt rename to kotlinx-coroutines-core/common/test/ParentCancellationTest.kt index 1e688d437b..23f2a10a97 100644 --- a/common/kotlinx-coroutines-core-common/test/ParentCancellationTest.kt +++ b/kotlinx-coroutines-core/common/test/ParentCancellationTest.kt @@ -40,56 +40,56 @@ class ParentCancellationTest : TestBase() { @Test fun testLaunchChild() = runTest { - testParentCancellation { fail -> + testParentCancellation(runsInScopeContext = true) { fail -> launch { fail() } } } @Test fun testAsyncChild() = runTest { - testParentCancellation { fail -> + testParentCancellation(runsInScopeContext = true) { fail -> async { fail() } } } @Test fun testProduceChild() = runTest { - testParentCancellation { fail -> + testParentCancellation(runsInScopeContext = true) { fail -> produce { fail() } } } @Test fun testBroadcastChild() = runTest { - testParentCancellation { fail -> + testParentCancellation(runsInScopeContext = true) { fail -> broadcast { fail() }.openSubscription() } } @Test fun testSupervisorChild() = runTest { - testParentCancellation(expectParentActive = true, expectUnhandled = true) { fail -> + testParentCancellation(expectParentActive = true, expectUnhandled = true, runsInScopeContext = true) { fail -> supervisorScope { fail() } } } @Test fun testCoroutineScopeChild() = runTest { - testParentCancellation(expectParentActive = true, expectRethrows = true) { fail -> + testParentCancellation(expectParentActive = true, expectRethrows = true, runsInScopeContext = true) { fail -> coroutineScope { fail() } } } @Test fun testWithContextChild() = runTest { - testParentCancellation(expectParentActive = true, expectRethrows = true) { fail -> + testParentCancellation(expectParentActive = true, expectRethrows = true, runsInScopeContext = true) { fail -> withContext(CoroutineName("fail")) { fail() } } } @Test fun testWithTimeoutChild() = runTest { - testParentCancellation(expectParentActive = true, expectRethrows = true) { fail -> + testParentCancellation(expectParentActive = true, expectRethrows = true, runsInScopeContext = true) { fail -> withTimeout(1000) { fail() } } } @@ -98,16 +98,32 @@ class ParentCancellationTest : TestBase() { expectParentActive: Boolean = false, expectRethrows: Boolean = false, expectUnhandled: Boolean = false, + runsInScopeContext: Boolean = false, child: suspend CoroutineScope.(block: suspend CoroutineScope.() -> Unit) -> Unit ) { - testWithException(expectParentActive, expectRethrows, expectUnhandled, TestException(), child) - testWithException(true, expectRethrows, false, CancellationException("Test"), child) + testWithException( + expectParentActive, + expectRethrows, + expectUnhandled, + runsInScopeContext, + TestException(), + child + ) + testWithException( + true, + expectRethrows, + false, + runsInScopeContext, + CancellationException("Test"), + child + ) } private suspend fun CoroutineScope.testWithException( expectParentActive: Boolean, expectRethrows: Boolean, expectUnhandled: Boolean, + runsInScopeContext: Boolean, throwException: Throwable, child: suspend CoroutineScope.(block: suspend CoroutineScope.() -> Unit) -> Unit ) { @@ -124,10 +140,10 @@ class ParentCancellationTest : TestBase() { throw throwException } grandchild.join() - if (expectUnhandled) { - assertSame(throwException, unhandledException) - } else { - assertNull(unhandledException) + when { + !expectParentActive && runsInScopeContext -> expectUnreached() + expectUnhandled -> assertSame(throwException, unhandledException) + else -> assertNull(unhandledException) } } if (expectRethrows && throwException !is CancellationException) { diff --git a/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt b/kotlinx-coroutines-core/common/test/SupervisorTest.kt similarity index 97% rename from common/kotlinx-coroutines-core-common/test/SupervisorTest.kt rename to kotlinx-coroutines-core/common/test/SupervisorTest.kt index 130623bfec..7fdd8fcbdd 100644 --- a/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt +++ b/kotlinx-coroutines-core/common/test/SupervisorTest.kt @@ -131,7 +131,6 @@ class SupervisorTest : TestBase() { } @Test - @Ignore // JS BE bug fun testSupervisorThrowsWithFailingChild() = runTest(unhandled = listOf({e -> e is TestException2})) { try { supervisorScope { @@ -168,7 +167,7 @@ class SupervisorTest : TestBase() { } expect(1) yield() - parent.cancel(TestException1()) + parent.completeExceptionally(TestException1()) try { deferred.await() expectUnreached() @@ -190,7 +189,7 @@ class SupervisorTest : TestBase() { fun testSupervisorWithParentCancelException() { val parent = Job() val supervisor = SupervisorJob(parent) - supervisor.cancel(TestException1()) + supervisor.completeExceptionally(TestException1()) assertTrue(supervisor.isCancelled) assertTrue(parent.isCancelled) } diff --git a/common/kotlinx-coroutines-core-common/test/TestBase.common.kt b/kotlinx-coroutines-core/common/test/TestBase.common.kt similarity index 88% rename from common/kotlinx-coroutines-core-common/test/TestBase.common.kt rename to kotlinx-coroutines-core/common/test/TestBase.common.kt index f705c79e73..9c162ff54f 100644 --- a/common/kotlinx-coroutines-core-common/test/TestBase.common.kt +++ b/kotlinx-coroutines-core/common/test/TestBase.common.kt @@ -28,8 +28,10 @@ public class TestException(message: String? = null) : Throwable(message), NonRec public class TestException1(message: String? = null) : Throwable(message), NonRecoverableThrowable public class TestException2(message: String? = null) : Throwable(message), NonRecoverableThrowable public class TestException3(message: String? = null) : Throwable(message), NonRecoverableThrowable +public class TestCancellationException(message: String? = null) : CancellationException(message), NonRecoverableThrowable public class TestRuntimeException(message: String? = null) : RuntimeException(message), NonRecoverableThrowable public class RecoverableTestException(message: String? = null) : RuntimeException(message) +public class RecoverableTestCancellationException(message: String? = null) : CancellationException(message) public fun wrapperDispatcher(context: CoroutineContext): CoroutineContext { val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher diff --git a/common/kotlinx-coroutines-core-common/test/Try.kt b/kotlinx-coroutines-core/common/test/Try.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/Try.kt rename to kotlinx-coroutines-core/common/test/Try.kt diff --git a/common/kotlinx-coroutines-core-common/test/UnconfinedTest.kt b/kotlinx-coroutines-core/common/test/UnconfinedTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/UnconfinedTest.kt rename to kotlinx-coroutines-core/common/test/UnconfinedTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/UndispatchedResultTest.kt b/kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/UndispatchedResultTest.kt rename to kotlinx-coroutines-core/common/test/UndispatchedResultTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/WithContextTest.kt b/kotlinx-coroutines-core/common/test/WithContextTest.kt similarity index 91% rename from common/kotlinx-coroutines-core-common/test/WithContextTest.kt rename to kotlinx-coroutines-core/common/test/WithContextTest.kt index b13d9b7bf2..be6bde4a09 100644 --- a/common/kotlinx-coroutines-core-common/test/WithContextTest.kt +++ b/kotlinx-coroutines-core/common/test/WithContextTest.kt @@ -3,7 +3,7 @@ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913 +@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-22237 package kotlinx.coroutines @@ -198,7 +198,7 @@ class WithContextTest : TestBase() { } @Test - fun testRunSelfCancellationWithException() = runTest(unhandled = listOf({e -> e is AssertionError})) { + fun testRunSelfCancellationWithException() = runTest { expect(1) var job: Job? = null job = launch(Job()) { @@ -208,13 +208,12 @@ class WithContextTest : TestBase() { require(isActive) expect(5) job!!.cancel() - require(job!!.cancel(AssertionError())) // cancel again, no success here require(!isActive) - throw TestException() // but throw a different exception + throw TestException() // but throw an exception } } catch (e: Throwable) { expect(7) - // make sure TestException, not CancellationException or AssertionError is thrown + // make sure TestException, not CancellationException is thrown assertTrue(e is TestException, "Caught $e") } } @@ -228,7 +227,7 @@ class WithContextTest : TestBase() { } @Test - fun testRunSelfCancellation() = runTest(unhandled = listOf({e -> e is AssertionError})) { + fun testRunSelfCancellation() = runTest { expect(1) var job: Job? = null job = launch(Job()) { @@ -238,14 +237,13 @@ class WithContextTest : TestBase() { require(isActive) expect(5) job!!.cancel() // cancel itself - require(job!!.cancel(AssertionError())) require(!isActive) "OK".wrap() } expectUnreached() } catch (e: Throwable) { expect(7) - // make sure JCE is thrown + // make sure CancellationException is thrown assertTrue(e is CancellationException, "Caught $e") } } @@ -325,6 +323,32 @@ class WithContextTest : TestBase() { assertFalse(ctxJob.isCancelled) } + @Test + fun testWithContextCancelledJob() = runTest { + expect(1) + val job = Job() + job.cancel() + try { + withContext(job) { + expectUnreached() + } + } catch (e: CancellationException) { + expect(2) + } + finish(3) + } + + @Test + fun testWithContextCancelledThisJob() = runTest( + expected = { it is CancellationException } + ) { + coroutineContext.cancel() + withContext(wrapperDispatcher(coroutineContext)) { + expectUnreached() + } + expectUnreached() + } + private class Wrapper(val value: String) : Incomplete { override val isActive: Boolean get() = error("") diff --git a/common/kotlinx-coroutines-core-common/test/WithTimeoutOrNullTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/WithTimeoutOrNullTest.kt rename to kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/WithTimeoutTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/WithTimeoutTest.kt rename to kotlinx-coroutines-core/common/test/WithTimeoutTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/channels/ArrayBroadcastChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt similarity index 96% rename from common/kotlinx-coroutines-core-common/test/channels/ArrayBroadcastChannelTest.kt rename to kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt index 0b3a22241c..9867ead560 100644 --- a/common/kotlinx-coroutines-core-common/test/channels/ArrayBroadcastChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt @@ -162,7 +162,9 @@ class ArrayBroadcastChannelTest : TestBase() { val channel = BroadcastChannel(1) // launch generator (for later) in this context launch { - for (x in 1..5) channel.send(x) + for (x in 1..5) { + channel.send(x) + } channel.close() } // start consuming @@ -188,10 +190,10 @@ class ArrayBroadcastChannelTest : TestBase() { } @Test - fun testCancelWithCause() = runTest({ it is TestException }) { + fun testCancelWithCause() = runTest({ it is TestCancellationException }) { val channel = BroadcastChannel(1) val subscription = channel.openSubscription() - subscription.cancel(TestException()) + subscription.cancel(TestCancellationException()) subscription.receiveOrNull() } diff --git a/common/kotlinx-coroutines-core-common/test/channels/ArrayChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt similarity index 81% rename from common/kotlinx-coroutines-core-common/test/channels/ArrayChannelTest.kt rename to kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt index 79f73aed97..308f8f3c2a 100644 --- a/common/kotlinx-coroutines-core-common/test/channels/ArrayChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt @@ -11,12 +11,10 @@ class ArrayChannelTest : TestBase() { @Test fun testSimple() = runTest { val q = Channel(1) - check(q.isEmpty && !q.isFull) expect(1) val sender = launch { expect(4) q.send(1) // success -- buffered - check(!q.isEmpty && q.isFull) expect(5) q.send(2) // suspends (buffer full) expect(9) @@ -25,7 +23,6 @@ class ArrayChannelTest : TestBase() { val receiver = launch { expect(6) check(q.receive() == 1) // does not suspend -- took from buffer - check(!q.isEmpty && q.isFull) // waiting sender's element moved to buffer expect(7) check(q.receive() == 2) // does not suspend (takes from sender) expect(8) @@ -33,21 +30,20 @@ class ArrayChannelTest : TestBase() { expect(3) sender.join() receiver.join() - check(q.isEmpty && !q.isFull) finish(10) } @Test fun testClosedBufferedReceiveOrNull() = runTest { val q = Channel(1) - check(q.isEmpty && !q.isFull && !q.isClosedForSend && !q.isClosedForReceive) + check(!q.isClosedForSend && !q.isClosedForReceive) expect(1) launch { expect(5) - check(!q.isEmpty && !q.isFull && q.isClosedForSend && !q.isClosedForReceive) + check(q.isClosedForSend && !q.isClosedForReceive) assertEquals(42, q.receiveOrNull()) expect(6) - check(!q.isEmpty && !q.isFull && q.isClosedForSend && q.isClosedForReceive) + check(q.isClosedForSend && q.isClosedForReceive) assertEquals(null, q.receiveOrNull()) expect(7) } @@ -56,9 +52,9 @@ class ArrayChannelTest : TestBase() { expect(3) q.close() // goes on expect(4) - check(!q.isEmpty && !q.isFull && q.isClosedForSend && !q.isClosedForReceive) + check(q.isClosedForSend && !q.isClosedForReceive) yield() - check(!q.isEmpty && !q.isFull && q.isClosedForSend && q.isClosedForReceive) + check(q.isClosedForSend && q.isClosedForReceive) finish(8) } @@ -139,9 +135,9 @@ class ArrayChannelTest : TestBase() { } @Test - fun testCancelWithCause() = runTest({ it is TestException }) { + fun testCancelWithCause() = runTest({ it is TestCancellationException }) { val channel = Channel(5) - channel.cancel(TestException()) + channel.cancel(TestCancellationException()) channel.receiveOrNull() } } diff --git a/common/kotlinx-coroutines-core-common/test/channels/BasicOperationsTest.kt b/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/channels/BasicOperationsTest.kt rename to kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/channels/BroadcastChannelFactoryTest.kt b/kotlinx-coroutines-core/common/test/channels/BroadcastChannelFactoryTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/channels/BroadcastChannelFactoryTest.kt rename to kotlinx-coroutines-core/common/test/channels/BroadcastChannelFactoryTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/channels/BroadcastTest.kt b/kotlinx-coroutines-core/common/test/channels/BroadcastTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/channels/BroadcastTest.kt rename to kotlinx-coroutines-core/common/test/channels/BroadcastTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/channels/ChannelFactoryTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelFactoryTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/channels/ChannelFactoryTest.kt rename to kotlinx-coroutines-core/common/test/channels/ChannelFactoryTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/channels/ChannelsTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelsTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/channels/ChannelsTest.kt rename to kotlinx-coroutines-core/common/test/channels/ChannelsTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/channels/ConflatedBroadcastChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/ConflatedBroadcastChannelTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/channels/ConflatedBroadcastChannelTest.kt rename to kotlinx-coroutines-core/common/test/channels/ConflatedBroadcastChannelTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/channels/ConflatedChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/ConflatedChannelTest.kt similarity index 94% rename from common/kotlinx-coroutines-core-common/test/channels/ConflatedChannelTest.kt rename to kotlinx-coroutines-core/common/test/channels/ConflatedChannelTest.kt index 61b5fc738a..666b706499 100644 --- a/common/kotlinx-coroutines-core-common/test/channels/ConflatedChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/ConflatedChannelTest.kt @@ -78,9 +78,9 @@ class ConflatedChannelTest : TestBase() { } @Test - fun testCancelWithCause() = runTest({ it is TestException }) { + fun testCancelWithCause() = runTest({ it is TestCancellationException }) { val channel = Channel(Channel.CONFLATED) - channel.cancel(TestException()) + channel.cancel(TestCancellationException()) channel.receiveOrNull() } } diff --git a/common/kotlinx-coroutines-core-common/test/channels/LinkedListChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt similarity index 88% rename from common/kotlinx-coroutines-core-common/test/channels/LinkedListChannelTest.kt rename to kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt index 763ed9bcde..700ea96c46 100644 --- a/common/kotlinx-coroutines-core-common/test/channels/LinkedListChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt @@ -35,9 +35,9 @@ class LinkedListChannelTest : TestBase() { } @Test - fun testCancelWithCause() = runTest({ it is TestException }) { + fun testCancelWithCause() = runTest({ it is TestCancellationException }) { val channel = Channel(Channel.UNLIMITED) - channel.cancel(TestException()) + channel.cancel(TestCancellationException()) channel.receiveOrNull() } } diff --git a/common/kotlinx-coroutines-core-common/test/channels/ProduceConsumeTest.kt b/kotlinx-coroutines-core/common/test/channels/ProduceConsumeTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/channels/ProduceConsumeTest.kt rename to kotlinx-coroutines-core/common/test/channels/ProduceConsumeTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/channels/ProduceTest.kt b/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt similarity index 94% rename from common/kotlinx-coroutines-core-common/test/channels/ProduceTest.kt rename to kotlinx-coroutines-core/common/test/channels/ProduceTest.kt index 9e01e81cd0..f286ba5d24 100644 --- a/common/kotlinx-coroutines-core-common/test/channels/ProduceTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt @@ -65,7 +65,7 @@ class ProduceTest : TestBase() { expectUnreached() } catch (e: Throwable) { expect(6) - check(e is TestException) + check(e is TestCancellationException) throw e } expectUnreached() @@ -73,11 +73,11 @@ class ProduceTest : TestBase() { expect(1) check(c.receive() == 1) expect(4) - c.cancel(TestException()) + c.cancel(TestCancellationException()) try { assertNull(c.receiveOrNull()) expectUnreached() - } catch (e: TestException) { + } catch (e: TestCancellationException) { expect(5) } yield() // to produce diff --git a/common/kotlinx-coroutines-core-common/test/channels/RendezvousChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt similarity index 93% rename from common/kotlinx-coroutines-core-common/test/channels/RendezvousChannelTest.kt rename to kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt index 7d8b421c02..fafda0d7da 100644 --- a/common/kotlinx-coroutines-core-common/test/channels/RendezvousChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt @@ -11,7 +11,6 @@ class RendezvousChannelTest : TestBase() { @Test fun testSimple() = runTest { val q = Channel(Channel.RENDEZVOUS) - check(q.isEmpty && q.isFull) expect(1) val sender = launch { expect(4) @@ -31,14 +30,13 @@ class RendezvousChannelTest : TestBase() { expect(3) sender.join() receiver.join() - check(q.isEmpty && q.isFull) finish(10) } @Test fun testClosedReceiveOrNull() = runTest { val q = Channel(Channel.RENDEZVOUS) - check(q.isEmpty && q.isFull && !q.isClosedForSend && !q.isClosedForReceive) + check(!q.isClosedForSend && !q.isClosedForReceive) expect(1) launch { expect(3) @@ -51,9 +49,9 @@ class RendezvousChannelTest : TestBase() { q.send(42) expect(5) q.close() - check(!q.isEmpty && !q.isFull && q.isClosedForSend && q.isClosedForReceive) + check(q.isClosedForSend && q.isClosedForReceive) yield() - check(!q.isEmpty && !q.isFull && q.isClosedForSend && q.isClosedForReceive) + check(q.isClosedForSend && q.isClosedForReceive) finish(7) } @@ -277,9 +275,9 @@ class RendezvousChannelTest : TestBase() { } @Test - fun testCancelWithCause() = runTest({ it is TestException }) { + fun testCancelWithCause() = runTest({ it is TestCancellationException }) { val channel = Channel(Channel.RENDEZVOUS) - channel.cancel(TestException()) + channel.cancel(TestCancellationException()) channel.receiveOrNull() } } diff --git a/common/kotlinx-coroutines-core-common/test/channels/SendReceiveStressTest.kt b/kotlinx-coroutines-core/common/test/channels/SendReceiveStressTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/channels/SendReceiveStressTest.kt rename to kotlinx-coroutines-core/common/test/channels/SendReceiveStressTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/channels/TestBroadcastChannelKind.kt b/kotlinx-coroutines-core/common/test/channels/TestBroadcastChannelKind.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/channels/TestBroadcastChannelKind.kt rename to kotlinx-coroutines-core/common/test/channels/TestBroadcastChannelKind.kt diff --git a/common/kotlinx-coroutines-core-common/test/channels/TestChannelKind.kt b/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt similarity index 84% rename from common/kotlinx-coroutines-core-common/test/channels/TestChannelKind.kt rename to kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt index 682058d3c0..6e1ee2bf69 100644 --- a/common/kotlinx-coroutines-core-common/test/channels/TestChannelKind.kt +++ b/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt @@ -4,6 +4,7 @@ package kotlinx.coroutines.channels +import kotlinx.coroutines.* import kotlinx.coroutines.selects.* enum class TestChannelKind { @@ -53,14 +54,20 @@ private class ChannelViaBroadcast( val sub = broadcast.openSubscription() override val isClosedForReceive: Boolean get() = sub.isClosedForReceive + @Suppress("DEPRECATION_ERROR") override val isEmpty: Boolean get() = sub.isEmpty override suspend fun receive(): E = sub.receive() override suspend fun receiveOrNull(): E? = sub.receiveOrNull() override fun poll(): E? = sub.poll() override fun iterator(): ChannelIterator = sub.iterator() - override fun cancel(): Unit = sub.cancel() - override fun cancel(cause: Throwable?): Boolean = sub.cancel(cause) + + override fun cancel(cause: CancellationException?) = sub.cancel(cause) + + // implementing hidden method anyway, so can cast to an internal class + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") + override fun cancel(cause: Throwable?): Boolean = (sub as AbstractChannel).cancelInternal(cause) + override val onReceive: SelectClause1 get() = sub.onReceive override val onReceiveOrNull: SelectClause1 diff --git a/common/kotlinx-coroutines-core-common/test/selects/SelectArrayChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/selects/SelectArrayChannelTest.kt rename to kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/selects/SelectBiasTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectBiasTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/selects/SelectBiasTest.kt rename to kotlinx-coroutines-core/common/test/selects/SelectBiasTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/selects/SelectBuilderImplTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectBuilderImplTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/selects/SelectBuilderImplTest.kt rename to kotlinx-coroutines-core/common/test/selects/SelectBuilderImplTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/selects/SelectDeferredTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectDeferredTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/selects/SelectDeferredTest.kt rename to kotlinx-coroutines-core/common/test/selects/SelectDeferredTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/selects/SelectJobTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectJobTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/selects/SelectJobTest.kt rename to kotlinx-coroutines-core/common/test/selects/SelectJobTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/selects/SelectMutexTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectMutexTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/selects/SelectMutexTest.kt rename to kotlinx-coroutines-core/common/test/selects/SelectMutexTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/selects/SelectRendezvousChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/selects/SelectRendezvousChannelTest.kt rename to kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/selects/SelectTimeoutTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectTimeoutTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/selects/SelectTimeoutTest.kt rename to kotlinx-coroutines-core/common/test/selects/SelectTimeoutTest.kt diff --git a/common/kotlinx-coroutines-core-common/test/sync/MutexTest.kt b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt similarity index 100% rename from common/kotlinx-coroutines-core-common/test/sync/MutexTest.kt rename to kotlinx-coroutines-core/common/test/sync/MutexTest.kt diff --git a/js/kotlinx-coroutines-core-js/npm/README.md b/kotlinx-coroutines-core/js/npm/README.md similarity index 81% rename from js/kotlinx-coroutines-core-js/npm/README.md rename to kotlinx-coroutines-core/js/npm/README.md index c048ea285c..23ee049a77 100644 --- a/js/kotlinx-coroutines-core-js/npm/README.md +++ b/kotlinx-coroutines-core/js/npm/README.md @@ -13,4 +13,4 @@ launch { ## Documentation * [Guide to kotlinx.coroutines by example on JVM](https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md) (**read it first**) -* [Full kotlinx.coroutines API reference](http://kotlin.github.io/kotlinx.coroutines) +* [Full kotlinx.coroutines API reference](https://kotlin.github.io/kotlinx.coroutines) diff --git a/js/kotlinx-coroutines-core-js/npm/package.json b/kotlinx-coroutines-core/js/npm/package.json similarity index 100% rename from js/kotlinx-coroutines-core-js/npm/package.json rename to kotlinx-coroutines-core/js/npm/package.json diff --git a/js/kotlinx-coroutines-core-js/src/CompletionHandler.kt b/kotlinx-coroutines-core/js/src/CompletionHandler.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/CompletionHandler.kt rename to kotlinx-coroutines-core/js/src/CompletionHandler.kt diff --git a/js/kotlinx-coroutines-core-js/src/CoroutineContext.kt b/kotlinx-coroutines-core/js/src/CoroutineContext.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/CoroutineContext.kt rename to kotlinx-coroutines-core/js/src/CoroutineContext.kt diff --git a/js/kotlinx-coroutines-core-js/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/CoroutineExceptionHandlerImpl.kt rename to kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt diff --git a/js/kotlinx-coroutines-core-js/src/Debug.kt b/kotlinx-coroutines-core/js/src/Debug.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/Debug.kt rename to kotlinx-coroutines-core/js/src/Debug.kt diff --git a/js/kotlinx-coroutines-core-js/src/Dispatchers.kt b/kotlinx-coroutines-core/js/src/Dispatchers.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/Dispatchers.kt rename to kotlinx-coroutines-core/js/src/Dispatchers.kt diff --git a/js/kotlinx-coroutines-core-js/src/EventLoop.kt b/kotlinx-coroutines-core/js/src/EventLoop.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/EventLoop.kt rename to kotlinx-coroutines-core/js/src/EventLoop.kt diff --git a/native/kotlinx-coroutines-core-native/src/Exceptions.kt b/kotlinx-coroutines-core/js/src/Exceptions.kt similarity index 75% rename from native/kotlinx-coroutines-core-native/src/Exceptions.kt rename to kotlinx-coroutines-core/js/src/Exceptions.kt index 561dc75681..83a0cdaf90 100644 --- a/native/kotlinx-coroutines-core-native/src/Exceptions.kt +++ b/kotlinx-coroutines-core/js/src/Exceptions.kt @@ -23,6 +23,13 @@ public actual class CompletionHandlerException public actual constructor( */ public actual open class CancellationException actual constructor(message: String?) : IllegalStateException(message) +/** + * Creates a cancellation exception with a specified message and [cause]. + */ +@Suppress("FunctionName") +public actual fun CancellationException(message: String?, cause: Throwable?) : CancellationException = + CancellationException(message.withCause(cause)) + /** * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled or completed * without cause, or with a cause or exception that is not [CancellationException] @@ -41,14 +48,21 @@ internal actual class JobCancellationException public actual constructor( (message!!.hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ?: 0) } -internal actual class DispatchException actual constructor(message: String, cause: Throwable) : RuntimeException(message.withCause(cause)) +internal actual class CoroutinesInternalError actual constructor(message: String, cause: Throwable) : Error(message.withCause(cause)) @Suppress("FunctionName") internal fun IllegalStateException(message: String, cause: Throwable?) = IllegalStateException(message.withCause(cause)) -private fun String.withCause(cause: Throwable?) = - if (cause == null) this else "$this; caused by $cause" +private fun String?.withCause(cause: Throwable?) = + when { + cause == null -> this + this == null -> "caused by $cause" + else -> "$this; caused by $cause" + } @Suppress("NOTHING_TO_INLINE") internal actual inline fun Throwable.addSuppressedThrowable(other: Throwable) { /* empty */ } + +// For use in tests +internal actual val RECOVER_STACK_TRACES: Boolean = false \ No newline at end of file diff --git a/js/kotlinx-coroutines-core-js/src/JSDispatcher.kt b/kotlinx-coroutines-core/js/src/JSDispatcher.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/JSDispatcher.kt rename to kotlinx-coroutines-core/js/src/JSDispatcher.kt diff --git a/js/kotlinx-coroutines-core-js/src/Promise.kt b/kotlinx-coroutines-core/js/src/Promise.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/Promise.kt rename to kotlinx-coroutines-core/js/src/Promise.kt diff --git a/js/kotlinx-coroutines-core-js/src/Runnable.kt b/kotlinx-coroutines-core/js/src/Runnable.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/Runnable.kt rename to kotlinx-coroutines-core/js/src/Runnable.kt diff --git a/js/kotlinx-coroutines-core-js/src/SchedulerTask.kt b/kotlinx-coroutines-core/js/src/SchedulerTask.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/SchedulerTask.kt rename to kotlinx-coroutines-core/js/src/SchedulerTask.kt diff --git a/js/kotlinx-coroutines-core-js/src/Window.kt b/kotlinx-coroutines-core/js/src/Window.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/Window.kt rename to kotlinx-coroutines-core/js/src/Window.kt diff --git a/js/kotlinx-coroutines-core-js/src/internal/ArrayCopy.kt b/kotlinx-coroutines-core/js/src/internal/ArrayCopy.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/internal/ArrayCopy.kt rename to kotlinx-coroutines-core/js/src/internal/ArrayCopy.kt diff --git a/js/kotlinx-coroutines-core-js/src/internal/Concurrent.kt b/kotlinx-coroutines-core/js/src/internal/Concurrent.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/internal/Concurrent.kt rename to kotlinx-coroutines-core/js/src/internal/Concurrent.kt diff --git a/js/kotlinx-coroutines-core-js/src/internal/CopyOnWriteList.kt b/kotlinx-coroutines-core/js/src/internal/CopyOnWriteList.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/internal/CopyOnWriteList.kt rename to kotlinx-coroutines-core/js/src/internal/CopyOnWriteList.kt diff --git a/js/kotlinx-coroutines-core-js/src/internal/LinkedList.kt b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/internal/LinkedList.kt rename to kotlinx-coroutines-core/js/src/internal/LinkedList.kt diff --git a/js/kotlinx-coroutines-core-js/src/internal/ProbesSupport.kt b/kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/internal/ProbesSupport.kt rename to kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt diff --git a/js/kotlinx-coroutines-core-js/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/internal/StackTraceRecovery.kt rename to kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt diff --git a/js/kotlinx-coroutines-core-js/src/internal/Synchronized.kt b/kotlinx-coroutines-core/js/src/internal/Synchronized.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/internal/Synchronized.kt rename to kotlinx-coroutines-core/js/src/internal/Synchronized.kt diff --git a/kotlinx-coroutines-core/js/src/internal/SystemProps.kt b/kotlinx-coroutines-core/js/src/internal/SystemProps.kt new file mode 100644 index 0000000000..6779d4e5d8 --- /dev/null +++ b/kotlinx-coroutines-core/js/src/internal/SystemProps.kt @@ -0,0 +1,7 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.internal + +internal actual fun systemProp(propertyName: String): String? = null \ No newline at end of file diff --git a/js/kotlinx-coroutines-core-js/src/internal/ThreadContext.kt b/kotlinx-coroutines-core/js/src/internal/ThreadContext.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/internal/ThreadContext.kt rename to kotlinx-coroutines-core/js/src/internal/ThreadContext.kt diff --git a/js/kotlinx-coroutines-core-js/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/src/internal/ThreadLocal.kt rename to kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt diff --git a/js/kotlinx-coroutines-core-js/test/MessageQueueTest.kt b/kotlinx-coroutines-core/js/test/MessageQueueTest.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/test/MessageQueueTest.kt rename to kotlinx-coroutines-core/js/test/MessageQueueTest.kt diff --git a/js/kotlinx-coroutines-core-js/test/PromiseTest.kt b/kotlinx-coroutines-core/js/test/PromiseTest.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/test/PromiseTest.kt rename to kotlinx-coroutines-core/js/test/PromiseTest.kt diff --git a/js/kotlinx-coroutines-core-js/test/TestBase.kt b/kotlinx-coroutines-core/js/test/TestBase.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/test/TestBase.kt rename to kotlinx-coroutines-core/js/test/TestBase.kt diff --git a/js/kotlinx-coroutines-core-js/test/internal/ArrayCopyKtTest.kt b/kotlinx-coroutines-core/js/test/internal/ArrayCopyKtTest.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/test/internal/ArrayCopyKtTest.kt rename to kotlinx-coroutines-core/js/test/internal/ArrayCopyKtTest.kt diff --git a/js/kotlinx-coroutines-core-js/test/internal/LinkedListTest.kt b/kotlinx-coroutines-core/js/test/internal/LinkedListTest.kt similarity index 100% rename from js/kotlinx-coroutines-core-js/test/internal/LinkedListTest.kt rename to kotlinx-coroutines-core/js/test/internal/LinkedListTest.kt diff --git a/core/kotlinx-coroutines-core/resources/META-INF/proguard/coroutines.pro b/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro similarity index 100% rename from core/kotlinx-coroutines-core/resources/META-INF/proguard/coroutines.pro rename to kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro diff --git a/core/kotlinx-coroutines-core/src/Builders.kt b/kotlinx-coroutines-core/jvm/src/Builders.kt similarity index 94% rename from core/kotlinx-coroutines-core/src/Builders.kt rename to kotlinx-coroutines-core/jvm/src/Builders.kt index 316ce3eafd..1f371cf04c 100644 --- a/core/kotlinx-coroutines-core/src/Builders.kt +++ b/kotlinx-coroutines-core/jvm/src/Builders.kt @@ -58,7 +58,10 @@ private class BlockingCoroutine( private val blockedThread: Thread, private val eventLoop: EventLoop? ) : AbstractCoroutine(parentContext, true) { - override fun onCompletionInternal(state: Any?, mode: Int, suppressed: Boolean) { + override val cancelsParent: Boolean + get() = false // it throws exception to parent instead of cancelling it + + override fun afterCompletionInternal(state: Any?, mode: Int) { // wake up blocked thread if (Thread.currentThread() != blockedThread) LockSupport.unpark(blockedThread) @@ -72,7 +75,7 @@ private class BlockingCoroutine( try { while (true) { @Suppress("DEPRECATION") - if (Thread.interrupted()) throw InterruptedException().also { cancel(it) } + if (Thread.interrupted()) throw InterruptedException().also { cancelCoroutine(it) } val parkNanos = eventLoop?.processNextEvent() ?: Long.MAX_VALUE // note: process next even may loose unpark flag, so check if completed before parking if (isCompleted) break diff --git a/core/kotlinx-coroutines-core/src/CommonPool.kt b/kotlinx-coroutines-core/jvm/src/CommonPool.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/CommonPool.kt rename to kotlinx-coroutines-core/jvm/src/CommonPool.kt diff --git a/core/kotlinx-coroutines-core/src/CompletionHandler.kt b/kotlinx-coroutines-core/jvm/src/CompletionHandler.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/CompletionHandler.kt rename to kotlinx-coroutines-core/jvm/src/CompletionHandler.kt diff --git a/core/kotlinx-coroutines-core/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/CoroutineContext.kt rename to kotlinx-coroutines-core/jvm/src/CoroutineContext.kt diff --git a/core/kotlinx-coroutines-core/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/CoroutineExceptionHandlerImpl.kt rename to kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt diff --git a/core/kotlinx-coroutines-core/src/Debug.kt b/kotlinx-coroutines-core/jvm/src/Debug.kt similarity index 67% rename from core/kotlinx-coroutines-core/src/Debug.kt rename to kotlinx-coroutines-core/jvm/src/Debug.kt index ec464b2d7b..3c750daea0 100644 --- a/core/kotlinx-coroutines-core/src/Debug.kt +++ b/kotlinx-coroutines-core/jvm/src/Debug.kt @@ -4,8 +4,8 @@ package kotlinx.coroutines -import kotlin.coroutines.Continuation import kotlinx.coroutines.internal.* +import kotlin.coroutines.* /** * Name of the property that controls coroutine debugging. See [newCoroutineContext][CoroutineScope.newCoroutineContext]. @@ -26,6 +26,33 @@ public const val DEBUG_PROPERTY_NAME = "kotlinx.coroutines.debug" */ internal const val STACKTRACE_RECOVERY_PROPERTY_NAME = "kotlinx.coroutines.stacktrace.recovery" +/** + * Throwable which can be cloned during stacktrace recovery in a class-specific way. + * For additional information about stacktrace recovery see [STACKTRACE_RECOVERY_PROPERTY_NAME] + * + * Example of usage: + * ``` + * class BadResponseCodeException(val responseCode: Int) : Exception(), CopyableThrowable { + * + * override fun createCopy(): BadResponseCodeException { + * val result = BadResponseCodeException(responseCode) + * result.initCause(this) + * return result + * } + * ``` + */ +@ExperimentalCoroutinesApi +public interface CopyableThrowable where T : Throwable, T : CopyableThrowable { + + /** + * Creates a copy of the current instance. + * For better debuggability, it is recommended to use original exception as [cause][Throwable.cause] of the resulting one. + * Stacktrace of copied exception will be overwritten by stacktrace recovery machinery by [Throwable.setStackTrace] call. + * An exception can opt-out of copying by returning `null` from this function. + */ + public fun createCopy(): T? +} + /** * Automatic debug configuration value for [DEBUG_PROPERTY_NAME]. See [newCoroutineContext][CoroutineScope.newCoroutineContext]. */ @@ -51,8 +78,9 @@ internal val DEBUG = systemProp(DEBUG_PROPERTY_NAME).let { value -> } } +// Note: stack-trace recovery is enabled only in debug mode @JvmField -internal val RECOVER_STACKTRACES = systemProp(STACKTRACE_RECOVERY_PROPERTY_NAME, true) +internal actual val RECOVER_STACK_TRACES = DEBUG && systemProp(STACKTRACE_RECOVERY_PROPERTY_NAME, true) // internal debugging tools diff --git a/core/kotlinx-coroutines-core/src/DefaultExecutor.kt b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/DefaultExecutor.kt rename to kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt diff --git a/core/kotlinx-coroutines-core/src/Dispatchers.kt b/kotlinx-coroutines-core/jvm/src/Dispatchers.kt similarity index 80% rename from core/kotlinx-coroutines-core/src/Dispatchers.kt rename to kotlinx-coroutines-core/jvm/src/Dispatchers.kt index 5c5ccca405..f4d8f42e1d 100644 --- a/core/kotlinx-coroutines-core/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/jvm/src/Dispatchers.kt @@ -56,22 +56,38 @@ public actual object Dispatchers { /** * A coroutine dispatcher that is not confined to any specific thread. - * It executes initial continuation of the coroutine _immediately_ in the current call-frame + * It executes initial continuation of the coroutine in the current call-frame * and lets the coroutine resume in whatever thread that is used by the corresponding suspending function, without - * mandating any specific threading policy. - * **Note: use with extreme caution, not for general code**. + * mandating any specific threading policy. Nested coroutines launched in this dispatcher form an event-loop to avoid + * stack overflows. + * + * ### Event loop + * Event loop semantics is a purely internal concept and have no guarantees on the order of execution + * except that all queued coroutines will be executed on the current thread in the lexical scope of the outermost + * unconfined coroutine. + * + * For example, the following code: + * ``` + * withContext(Dispatcher.Unconfined) { + * println(1) + * withContext(Dispatcher.Unconfined) { // Nested unconfined + * println(2) + * } + * println(3) + * } + * println("Done") + * ``` + * Can print both "1 2 3" and "1 3 2", this is an implementation detail that can be changed. + * But it is guaranteed that "Done" will be printed only when both `withContext` are completed. + * * * Note, that if you need your coroutine to be confined to a particular thread or a thread-pool after resumption, * but still want to execute it in the current call-frame until its first suspension, then you can use * an optional [CoroutineStart] parameter in coroutine builders like * [launch][CoroutineScope.launch] and [async][CoroutineScope.async] setting it to the * the value of [CoroutineStart.UNDISPATCHED]. - * - * **Note: This is an experimental api.** - * Semantics, order of execution, and particular implementation details of this dispatcher may change in the future. */ @JvmStatic - @ExperimentalCoroutinesApi public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined /** diff --git a/core/kotlinx-coroutines-core/src/EventLoop.kt b/kotlinx-coroutines-core/jvm/src/EventLoop.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/EventLoop.kt rename to kotlinx-coroutines-core/jvm/src/EventLoop.kt diff --git a/core/kotlinx-coroutines-core/src/Exceptions.kt b/kotlinx-coroutines-core/jvm/src/Exceptions.kt similarity index 73% rename from core/kotlinx-coroutines-core/src/Exceptions.kt rename to kotlinx-coroutines-core/jvm/src/Exceptions.kt index 0138cfe467..4e734987aa 100644 --- a/core/kotlinx-coroutines-core/src/Exceptions.kt +++ b/kotlinx-coroutines-core/jvm/src/Exceptions.kt @@ -2,6 +2,8 @@ * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +@file:Suppress("FunctionName") + package kotlinx.coroutines /** @@ -23,6 +25,13 @@ public actual class CompletionHandlerException actual constructor( */ public actual typealias CancellationException = java.util.concurrent.CancellationException +/** + * Creates a cancellation exception with a specified message and [cause]. + */ +@Suppress("FunctionName") +public actual fun CancellationException(message: String?, cause: Throwable?) : CancellationException = + CancellationException(message).apply { initCause(cause) } + /** * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled or completed * without cause, or with a cause or exception that is not [CancellationException] @@ -32,7 +41,7 @@ internal actual class JobCancellationException public actual constructor( message: String, cause: Throwable?, @JvmField internal actual val job: Job -) : CancellationException(message) { +) : CancellationException(message), CopyableThrowable { init { if (cause != null) initCause(cause) @@ -51,6 +60,17 @@ internal actual class JobCancellationException public actual constructor( return this } + override fun createCopy(): JobCancellationException? { + if (DEBUG) { + return JobCancellationException(message!!, this, job) + } + + /* + * In non-debug mode we don't copy JCE for speed as it does not have the stack trace anyway. + */ + return null + } + override fun toString(): String = "${super.toString()}; job=$job" @Suppress("DEPRECATION") @@ -61,7 +81,7 @@ internal actual class JobCancellationException public actual constructor( (message!!.hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ?: 0) } -internal actual class DispatchException actual constructor(message: String, cause: Throwable) : RuntimeException(message, cause) +internal actual class CoroutinesInternalError actual constructor(message: String, cause: Throwable) : Error(message, cause) @Suppress("NOTHING_TO_INLINE") internal actual inline fun Throwable.addSuppressedThrowable(other: Throwable) = diff --git a/core/kotlinx-coroutines-core/src/Executors.kt b/kotlinx-coroutines-core/jvm/src/Executors.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/Executors.kt rename to kotlinx-coroutines-core/jvm/src/Executors.kt diff --git a/core/kotlinx-coroutines-core/src/Future.kt b/kotlinx-coroutines-core/jvm/src/Future.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/Future.kt rename to kotlinx-coroutines-core/jvm/src/Future.kt diff --git a/core/kotlinx-coroutines-core/src/Runnable.kt b/kotlinx-coroutines-core/jvm/src/Runnable.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/Runnable.kt rename to kotlinx-coroutines-core/jvm/src/Runnable.kt diff --git a/core/kotlinx-coroutines-core/src/SchedulerTask.kt b/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/SchedulerTask.kt rename to kotlinx-coroutines-core/jvm/src/SchedulerTask.kt diff --git a/core/kotlinx-coroutines-core/src/ThreadContextElement.kt b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt similarity index 64% rename from core/kotlinx-coroutines-core/src/ThreadContextElement.kt rename to kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt index 959bc38c3a..4e8b6cc42e 100644 --- a/core/kotlinx-coroutines-core/src/ThreadContextElement.kt +++ b/kotlinx-coroutines-core/jvm/src/ThreadContextElement.kt @@ -40,7 +40,7 @@ import kotlin.coroutines.* * } * * // Usage - * launch(UI + CoroutineName("Progress bar coroutine")) { ... } + * launch(Dispatchers.Main + CoroutineName("Progress bar coroutine")) { ... } * ``` * * Every time this coroutine is resumed on a thread, UI thread name is updated to @@ -56,7 +56,7 @@ public interface ThreadContextElement : CoroutineContext.Element { * when the context of the coroutine this element. * The result of this function is the old value of the thread-local state that will be passed to [restoreThreadContext]. * This method should handle its own exceptions and do not rethrow it. Thrown exceptions will leave coroutine which - * context is updated in an undefined state. + * context is updated in an undefined state and may crash an application. * * @param context the coroutine context. */ @@ -69,7 +69,7 @@ public interface ThreadContextElement : CoroutineContext.Element { * The value of [oldState] is the result of the previous invocation of [updateThreadContext] and it should * be restored in the thread-local state by this function. * This method should handle its own exceptions and do not rethrow it. Thrown exceptions will leave coroutine which - * context is updated in an undefined state. + * context is updated in an undefined state and may crash an application. * * @param context the coroutine context. * @param oldState the value returned by the previous invocation of [updateThreadContext]. @@ -81,27 +81,29 @@ public interface ThreadContextElement : CoroutineContext.Element { * Wraps [ThreadLocal] into [ThreadContextElement]. The resulting [ThreadContextElement] * maintains the given [value] of the given [ThreadLocal] for coroutine regardless of the actual thread its is resumed on. * By default [ThreadLocal.get] is used as a value for the thread-local variable, but it can be overridden with [value] parameter. + * Beware that context element **does not track** modifications of the thread-local and accessing thread-local from coroutine + * without the corresponding context element returns **undefined** value. See the examples for a detailed description. * - * Example usage looks like this: * + * Example usage: * ``` * val myThreadLocal = ThreadLocal() * ... * println(myThreadLocal.get()) // Prints "null" * launch(Dispatchers.Default + myThreadLocal.asContextElement(value = "foo")) { * println(myThreadLocal.get()) // Prints "foo" - * withContext(UI) { + * withContext(Dispatchers.Main) { * println(myThreadLocal.get()) // Prints "foo", but it's on UI thread * } * } * println(myThreadLocal.get()) // Prints "null" * ``` * - * Note that the context element does not track modifications of the thread-local variable, for example: + * The context element does not track modifications of the thread-local variable, for example: * * ``` * myThreadLocal.set("main") - * withContext(UI) { + * withContext(Dispatchers.Main) { * println(myThreadLocal.get()) // Prints "main" * myThreadLocal.set("UI") * } @@ -109,12 +111,64 @@ public interface ThreadContextElement : CoroutineContext.Element { * ``` * * Use `withContext` to update the corresponding thread-local variable to a different value, for example: - * * ``` * withContext(myThreadLocal.asContextElement("foo")) { * println(myThreadLocal.get()) // Prints "foo" * } * ``` + * + * Accessing the thread-local without corresponding context element leads to undefined value: + * ``` + * val tl = ThreadLocal.withInitial { "initial" } + * + * runBlocking { + * println(tl.get()) // Will print "initial" + * // Change context + * withContext(tl.asContextElement("modified")) { + * println(tl.get()) // Will print "modified" + * } + * // Context is changed again + * println(tl.get()) // <- WARN: can print either "modified" or "initial" + * } + * ``` + * to fix this behaviour use `runBlocking(tl.asContextElement())` */ public fun ThreadLocal.asContextElement(value: T = get()): ThreadContextElement = ThreadLocalElement(value, this) + +/** + * Return `true` when current thread local is present in the coroutine context, `false` otherwise. + * Thread local can be present in the context only if it was added via [asContextElement] to the context. + * + * Example of usage: + * ``` + * suspend fun processRequest() { + * if (traceCurrentRequestThreadLocal.isPresent()) { // Probabilistic tracing + * // Do some heavy-weight tracing + * } + * // Process request regularly + * } + * ``` + */ +public suspend inline fun ThreadLocal<*>.isPresent(): Boolean = coroutineContext[ThreadLocalKey(this)] !== null + +/** + * Checks whether current thread local is present in the coroutine context and throws [IllegalStateException] if it is not. + * It is a good practice to validate that thread local is present in the context, especially in large code-bases, + * to avoid stale thread-local values and to have a strict invariants. + * + * E.g. one may use the following method to enforce proper use of the thread locals with coroutines: + * ``` + * public suspend inline fun ThreadLocal.getSafely(): T { + * ensurePresent() + * return get() + * } + * + * // Usage + * withContext(...) { + * val value = threadLocal.getSafely() // Fail-fast in case of improper context + * } + * ``` + */ +public suspend inline fun ThreadLocal<*>.ensurePresent(): Unit = + check(isPresent()) { "ThreadLocal $this is missing from context $coroutineContext" } diff --git a/core/kotlinx-coroutines-core/src/ThreadPoolDispatcher.kt b/kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/ThreadPoolDispatcher.kt rename to kotlinx-coroutines-core/jvm/src/ThreadPoolDispatcher.kt diff --git a/core/kotlinx-coroutines-core/src/TimeSource.kt b/kotlinx-coroutines-core/jvm/src/TimeSource.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/TimeSource.kt rename to kotlinx-coroutines-core/jvm/src/TimeSource.kt diff --git a/core/kotlinx-coroutines-core/src/channels/Actor.kt b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt similarity index 93% rename from core/kotlinx-coroutines-core/src/channels/Actor.kt rename to kotlinx-coroutines-core/jvm/src/channels/Actor.kt index bcf25dcd49..2d16225317 100644 --- a/core/kotlinx-coroutines-core/src/channels/Actor.kt +++ b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt @@ -127,13 +127,18 @@ private open class ActorCoroutine( channel: Channel, active: Boolean ) : ChannelCoroutine(parentContext, channel, active), ActorScope { - override fun onCancellation(cause: Throwable?) { - @Suppress("DEPRECATION") - _channel.cancel(cause) + override val cancelsParent: Boolean get() = true + + override fun onCancelling(cause: Throwable?) { + _channel.cancel(cause?.let { + it as? CancellationException ?: CancellationException("$classSimpleName was cancelled", it) + }) } - override val cancelsParent: Boolean get() = true - override fun handleJobException(exception: Throwable) = handleExceptionViaHandler(parentContext, exception) + override fun handleJobException(exception: Throwable): Boolean { + handleCoroutineException(context, exception) + return true + } } private class LazyActorCoroutine( @@ -156,6 +161,11 @@ private class LazyActorCoroutine( return super.offer(element) } + override fun close(cause: Throwable?): Boolean { + start() + return super.close(cause) + } + override val onSend: SelectClause2> get() = this diff --git a/core/kotlinx-coroutines-core/src/channels/Channels.kt b/kotlinx-coroutines-core/jvm/src/channels/Channels.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/channels/Channels.kt rename to kotlinx-coroutines-core/jvm/src/channels/Channels.kt diff --git a/core/kotlinx-coroutines-core/src/channels/TickerChannels.kt b/kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/channels/TickerChannels.kt rename to kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt diff --git a/core/kotlinx-coroutines-core/src/internal/ArrayCopy.kt b/kotlinx-coroutines-core/jvm/src/internal/ArrayCopy.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/internal/ArrayCopy.kt rename to kotlinx-coroutines-core/jvm/src/internal/ArrayCopy.kt diff --git a/core/kotlinx-coroutines-core/src/internal/Concurrent.kt b/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/internal/Concurrent.kt rename to kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt diff --git a/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt b/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt new file mode 100644 index 0000000000..8d11c6fdb7 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt @@ -0,0 +1,81 @@ +/* + * 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.coroutines.* +import java.lang.reflect.* +import java.util.* +import java.util.concurrent.locks.* +import kotlin.concurrent.* + +private val throwableFields = Throwable::class.java.fieldsCountOrDefault(-1) +private val cacheLock = ReentrantReadWriteLock() +private typealias Ctor = (Throwable) -> Throwable? +// Replace it with ClassValue when Java 6 support is over +private val exceptionCtors: WeakHashMap, Ctor> = WeakHashMap() + +@Suppress("UNCHECKED_CAST") +internal fun tryCopyException(exception: E): E? { + // Fast path for CopyableThrowable + if (exception is CopyableThrowable<*>) { + return runCatching { exception.createCopy() as E? }.getOrNull() + } + // Use cached ctor if found + cacheLock.read { exceptionCtors[exception.javaClass] }?.let { cachedCtor -> + return cachedCtor(exception) as E? + } + /* + * Skip reflective copy if an exception has additional fields (that are usually populated in user-defined constructors) + */ + if (throwableFields != exception.javaClass.fieldsCountOrDefault(0)) { + cacheLock.write { exceptionCtors[exception.javaClass] = { null } } + return null + } + /* + * Try to reflectively find constructor(), constructor(message, cause), constructor(cause) or constructor(message). + * Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace. + */ + var ctor: Ctor? = null + val constructors = exception.javaClass.constructors.sortedByDescending { it.parameterTypes.size } + for (constructor in constructors) { + ctor = createConstructor(constructor) + if (ctor != null) break + } + // Store the resulting ctor to cache + cacheLock.write { exceptionCtors[exception.javaClass] = ctor ?: { null } } + return ctor?.invoke(exception) as E? +} + +private fun createConstructor(constructor: Constructor<*>): Ctor? { + val p = constructor.parameterTypes + return when (p.size) { + 2 -> when { + p[0] == String::class.java && p[1] == Throwable::class.java -> + safeCtor { e -> constructor.newInstance(e.message, e) as Throwable } + else -> null + } + 1 -> when (p[0]) { + Throwable::class.java -> + safeCtor { e -> constructor.newInstance(e) as Throwable } + String::class.java -> + safeCtor { e -> (constructor.newInstance(e.message) as Throwable).also { it.initCause(e) } } + else -> null + } + 0 -> safeCtor { e -> (constructor.newInstance() as Throwable).also { it.initCause(e) } } + else -> null + } +} + +private inline fun safeCtor(crossinline block: (Throwable) -> Throwable): Ctor = + { e -> runCatching { block(e) }.getOrNull() } + +private fun Class<*>.fieldsCountOrDefault(defaultValue: Int) = kotlin.runCatching { fieldsCount() }.getOrDefault(defaultValue) + +private tailrec fun Class<*>.fieldsCount(accumulator: Int = 0): Int { + val fieldsCount = declaredFields.count { !Modifier.isStatic(it.modifiers) } + val totalFields = accumulator + fieldsCount + val superClass = superclass ?: return totalFields + return superClass.fieldsCount(totalFields) +} diff --git a/core/kotlinx-coroutines-core/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt similarity index 99% rename from core/kotlinx-coroutines-core/src/internal/LockFreeLinkedList.kt rename to kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt index 5c8446f65b..7d765b9701 100644 --- a/core/kotlinx-coroutines-core/src/internal/LockFreeLinkedList.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt @@ -41,7 +41,7 @@ public actual typealias AbstractAtomicDesc = LockFreeLinkedListNode.AbstractAtom /** * Doubly-linked concurrent list node with remove support. * Based on paper - * ["Lock-Free and Practical Doubly Linked List-Based Deques Using Single-Word Compare-and-Swap"](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.140.4693&rep=rep1&type=pdf) + * ["Lock-Free and Practical Doubly Linked List-Based Deques Using Single-Word Compare-and-Swap"](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.140.4693&rep=rep1&type=pdf) * by Sundell and Tsigas. * * Important notes: diff --git a/core/kotlinx-coroutines-core/src/internal/LockFreeTaskQueue.kt b/kotlinx-coroutines-core/jvm/src/internal/LockFreeTaskQueue.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/internal/LockFreeTaskQueue.kt rename to kotlinx-coroutines-core/jvm/src/internal/LockFreeTaskQueue.kt diff --git a/core/kotlinx-coroutines-core/src/internal/MainDispatchers.kt b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/internal/MainDispatchers.kt rename to kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt diff --git a/core/kotlinx-coroutines-core/src/internal/ProbesSupport.kt b/kotlinx-coroutines-core/jvm/src/internal/ProbesSupport.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/internal/ProbesSupport.kt rename to kotlinx-coroutines-core/jvm/src/internal/ProbesSupport.kt diff --git a/core/kotlinx-coroutines-core/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt similarity index 95% rename from core/kotlinx-coroutines-core/src/internal/StackTraceRecovery.kt rename to kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt index 9457130164..4727b26d5b 100644 --- a/core/kotlinx-coroutines-core/src/internal/StackTraceRecovery.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt @@ -12,9 +12,7 @@ import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* internal actual fun recoverStackTrace(exception: E): E { - if (recoveryDisabled(exception)) { - return exception - } + if (recoveryDisabled(exception)) return exception // No unwrapping on continuation-less path: exception is not reported multiple times via slow paths val copy = tryCopyException(exception) ?: return exception return copy.sanitizeStackTrace() @@ -41,10 +39,7 @@ private fun E.sanitizeStackTrace(): E { } internal actual fun recoverStackTrace(exception: E, continuation: Continuation<*>): E { - if (recoveryDisabled(exception) || continuation !is CoroutineStackFrame) { - return exception - } - + if (recoveryDisabled(exception) || continuation !is CoroutineStackFrame) return exception return recoverFromStackFrame(exception, continuation) } @@ -146,26 +141,23 @@ internal actual suspend inline fun recoverAndThrow(exception: Throwable): Nothin } internal actual fun unwrap(exception: E): E { - if (recoveryDisabled(exception)) { - return exception - } - + if (recoveryDisabled(exception)) return exception val cause = exception.cause // Fast-path to avoid array cloning if (cause == null || cause.javaClass != exception.javaClass) { return exception } - + // Slow path looks for artificial frames in a stack-trace if (exception.stackTrace.any { it.isArtificial() }) { @Suppress("UNCHECKED_CAST") - return exception.cause as? E ?: exception + return cause as E } else { return exception } } private fun recoveryDisabled(exception: E) = - !RECOVER_STACKTRACES || !DEBUG || exception is CancellationException || exception is NonRecoverableThrowable + !RECOVER_STACK_TRACES || exception is NonRecoverableThrowable private fun createStackTrace(continuation: CoroutineStackFrame): ArrayDeque { val stack = ArrayDeque() diff --git a/core/kotlinx-coroutines-core/src/internal/Synchronized.kt b/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/internal/Synchronized.kt rename to kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt diff --git a/kotlinx-coroutines-core/jvm/src/internal/SystemProps.kt b/kotlinx-coroutines-core/jvm/src/internal/SystemProps.kt new file mode 100644 index 0000000000..ef5ab24118 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/src/internal/SystemProps.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. + */ + +@file:JvmName("SystemPropsKt") +@file:JvmMultifileClass + +package kotlinx.coroutines.internal + +// number of processors at startup for consistent prop initialization +internal val AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors() + +internal actual fun systemProp( + propertyName: String +): String? = + try { + System.getProperty(propertyName) + } catch (e: SecurityException) { + null + } diff --git a/core/kotlinx-coroutines-core/src/internal/ThreadContext.kt b/kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt similarity index 97% rename from core/kotlinx-coroutines-core/src/internal/ThreadContext.kt rename to kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt index 7dafb4711f..375dc60b66 100644 --- a/core/kotlinx-coroutines-core/src/internal/ThreadContext.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/ThreadContext.kt @@ -98,7 +98,8 @@ internal fun restoreThreadContext(context: CoroutineContext, oldState: Any?) { } // top-level data class for a nicer out-of-the-box toString representation and class name -private data class ThreadLocalKey(private val threadLocal: ThreadLocal<*>) : CoroutineContext.Key> +@PublishedApi +internal data class ThreadLocalKey(private val threadLocal: ThreadLocal<*>) : CoroutineContext.Key> internal class ThreadLocalElement( private val value: T, diff --git a/core/kotlinx-coroutines-core/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/internal/ThreadLocal.kt rename to kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt diff --git a/core/kotlinx-coroutines-core/src/internal/ThreadSafeHeap.kt b/kotlinx-coroutines-core/jvm/src/internal/ThreadSafeHeap.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/internal/ThreadSafeHeap.kt rename to kotlinx-coroutines-core/jvm/src/internal/ThreadSafeHeap.kt diff --git a/core/kotlinx-coroutines-core/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt similarity index 99% rename from core/kotlinx-coroutines-core/src/scheduling/CoroutineScheduler.kt rename to kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt index 18a91ad930..59c8250936 100644 --- a/core/kotlinx-coroutines-core/src/scheduling/CoroutineScheduler.kt +++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt @@ -299,7 +299,7 @@ internal class CoroutineScheduler( // atomically set termination flag which is checked when workers are added or removed if (!_isTerminated.compareAndSet(0, 1)) return // make sure we are not waiting for the current thread - val currentWorker = Thread.currentThread() as? Worker + val currentWorker = currentWorker() // Capture # of created workers that cannot change anymore (mind the synchronized block!) val created = synchronized(workers) { createdWorkers } // Shutdown all workers with the only exception of the current thread @@ -481,9 +481,7 @@ internal class CoroutineScheduler( * Returns [ADDED], or [NOT_ADDED], or [ADDED_REQUIRES_HELP]. */ private fun submitToLocalQueue(task: Task, fair: Boolean): Int { - val worker = Thread.currentThread() as? Worker - ?: return NOT_ADDED - if (worker.scheduler !== this) return NOT_ADDED // different scheduler's worker (!!!) + val worker = currentWorker() ?: return NOT_ADDED /* * This worker could have been already terminated from this thread by close/shutdown and it should not @@ -533,6 +531,8 @@ internal class CoroutineScheduler( return ADDED_REQUIRES_HELP } + private fun currentWorker(): Worker? = (Thread.currentThread() as? Worker)?.takeIf { it.scheduler == this } + /** * Returns a string identifying the state of this scheduler for nicer debugging. * Note that this method is not atomic and represents rough state of pool. diff --git a/core/kotlinx-coroutines-core/src/scheduling/Dispatcher.kt b/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/scheduling/Dispatcher.kt rename to kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt diff --git a/core/kotlinx-coroutines-core/src/scheduling/Tasks.kt b/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/scheduling/Tasks.kt rename to kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt diff --git a/core/kotlinx-coroutines-core/src/scheduling/WorkQueue.kt b/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/scheduling/WorkQueue.kt rename to kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt diff --git a/core/kotlinx-coroutines-core/src/test_/TestCoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt similarity index 100% rename from core/kotlinx-coroutines-core/src/test_/TestCoroutineContext.kt rename to kotlinx-coroutines-core/jvm/src/test_/TestCoroutineContext.kt diff --git a/core/kotlinx-coroutines-core/test/AsyncJvmTest.kt b/kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/AsyncJvmTest.kt rename to kotlinx-coroutines-core/jvm/test/AsyncJvmTest.kt diff --git a/core/kotlinx-coroutines-core/test/AtomicCancellationTest.kt b/kotlinx-coroutines-core/jvm/test/AtomicCancellationTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/AtomicCancellationTest.kt rename to kotlinx-coroutines-core/jvm/test/AtomicCancellationTest.kt diff --git a/core/kotlinx-coroutines-core/test/AwaitJvmTest.kt b/kotlinx-coroutines-core/jvm/test/AwaitJvmTest.kt similarity index 91% rename from core/kotlinx-coroutines-core/test/AwaitJvmTest.kt rename to kotlinx-coroutines-core/jvm/test/AwaitJvmTest.kt index 8f8ef09644..c6b57c8f6b 100644 --- a/core/kotlinx-coroutines-core/test/AwaitJvmTest.kt +++ b/kotlinx-coroutines-core/jvm/test/AwaitJvmTest.kt @@ -12,7 +12,7 @@ class AwaitJvmTest : TestBase() { // This test is to make sure that handlers installed on the second deferred do not leak val d1 = CompletableDeferred() val d2 = CompletableDeferred() - d1.cancel(TestException()) // first is crashed + d1.completeExceptionally(TestException()) // first is crashed val iterations = 3_000_000 * stressTestMultiplier for (iter in 1..iterations) { try { diff --git a/core/kotlinx-coroutines-core/test/AwaitStressTest.kt b/kotlinx-coroutines-core/jvm/test/AwaitStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/AwaitStressTest.kt rename to kotlinx-coroutines-core/jvm/test/AwaitStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/CancellableContinuationJvmTest.kt b/kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/CancellableContinuationJvmTest.kt rename to kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt diff --git a/core/kotlinx-coroutines-core/test/CancelledAwaitStressTest.kt b/kotlinx-coroutines-core/jvm/test/CancelledAwaitStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/CancelledAwaitStressTest.kt rename to kotlinx-coroutines-core/jvm/test/CancelledAwaitStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/CommonPoolTest.kt b/kotlinx-coroutines-core/jvm/test/CommonPoolTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/CommonPoolTest.kt rename to kotlinx-coroutines-core/jvm/test/CommonPoolTest.kt diff --git a/core/kotlinx-coroutines-core/test/ContinuationSerializationTest.kt b/kotlinx-coroutines-core/jvm/test/ContinuationSerializationTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/ContinuationSerializationTest.kt rename to kotlinx-coroutines-core/jvm/test/ContinuationSerializationTest.kt diff --git a/core/kotlinx-coroutines-core/test/CoroutinesJvmTest.kt b/kotlinx-coroutines-core/jvm/test/CoroutinesJvmTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/CoroutinesJvmTest.kt rename to kotlinx-coroutines-core/jvm/test/CoroutinesJvmTest.kt diff --git a/core/kotlinx-coroutines-core/test/DebugThreadNameTest.kt b/kotlinx-coroutines-core/jvm/test/DebugThreadNameTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/DebugThreadNameTest.kt rename to kotlinx-coroutines-core/jvm/test/DebugThreadNameTest.kt diff --git a/core/kotlinx-coroutines-core/test/DefaultExecutorStressTest.kt b/kotlinx-coroutines-core/jvm/test/DefaultExecutorStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/DefaultExecutorStressTest.kt rename to kotlinx-coroutines-core/jvm/test/DefaultExecutorStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/DelayJvmTest.kt b/kotlinx-coroutines-core/jvm/test/DelayJvmTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/DelayJvmTest.kt rename to kotlinx-coroutines-core/jvm/test/DelayJvmTest.kt diff --git a/core/kotlinx-coroutines-core/test/EventLoopsTest.kt b/kotlinx-coroutines-core/jvm/test/EventLoopsTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/EventLoopsTest.kt rename to kotlinx-coroutines-core/jvm/test/EventLoopsTest.kt diff --git a/core/kotlinx-coroutines-core/test/ExecutorsTest.kt b/kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/ExecutorsTest.kt rename to kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt diff --git a/core/kotlinx-coroutines-core/test/FailFastOnStartTest.kt b/kotlinx-coroutines-core/jvm/test/FailFastOnStartTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/FailFastOnStartTest.kt rename to kotlinx-coroutines-core/jvm/test/FailFastOnStartTest.kt diff --git a/kotlinx-coroutines-core/jvm/test/FailingCoroutinesMachineryTest.kt b/kotlinx-coroutines-core/jvm/test/FailingCoroutinesMachineryTest.kt new file mode 100644 index 0000000000..9bf8ffad85 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/FailingCoroutinesMachineryTest.kt @@ -0,0 +1,135 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import org.junit.* +import org.junit.Test +import org.junit.runner.* +import org.junit.runners.* +import java.util.concurrent.* +import kotlin.coroutines.* +import kotlin.test.* + +@RunWith(Parameterized::class) +class FailingCoroutinesMachineryTest( + private val element: CoroutineContext.Element, + private val dispatcher: CoroutineDispatcher +) : TestBase() { + + private var caught: Throwable? = null + private val latch = CountDownLatch(1) + private var exceptionHandler = CoroutineExceptionHandler { _, t -> caught = t;latch.countDown() } + private val lazyOuterDispatcher = lazy { newFixedThreadPoolContext(1, "") } + + private object FailingUpdate : ThreadContextElement { + private object Key : CoroutineContext.Key + + override val key: CoroutineContext.Key<*> get() = Key + + override fun restoreThreadContext(context: CoroutineContext, oldState: Unit) { + } + + override fun updateThreadContext(context: CoroutineContext) { + throw TestException("Prevent a coroutine from starting right here for some reason") + } + + override fun toString() = "FailingUpdate" + } + + private object FailingRestore : ThreadContextElement { + private object Key : CoroutineContext.Key + + override val key: CoroutineContext.Key<*> get() = Key + + override fun restoreThreadContext(context: CoroutineContext, oldState: Unit) { + throw TestException("Prevent a coroutine from starting right here for some reason") + } + + override fun updateThreadContext(context: CoroutineContext) { + } + + override fun toString() = "FailingRestore" + } + + private object ThrowingDispatcher : CoroutineDispatcher() { + override fun dispatch(context: CoroutineContext, block: Runnable) { + throw TestException() + } + + override fun toString() = "ThrowingDispatcher" + } + + private object ThrowingDispatcher2 : CoroutineDispatcher() { + override fun dispatch(context: CoroutineContext, block: Runnable) { + block.run() + } + + override fun isDispatchNeeded(context: CoroutineContext): Boolean { + throw TestException() + } + + override fun toString() = "ThrowingDispatcher2" + } + + @After + fun tearDown() { + runCatching { (dispatcher as? ExecutorCoroutineDispatcher)?.close() } + if (lazyOuterDispatcher.isInitialized()) lazyOuterDispatcher.value.close() + } + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "Element: {0}, dispatcher: {1}") + fun dispatchers(): List> { + val elements = listOf(FailingRestore, FailingUpdate) + val dispatchers = listOf( + Dispatchers.Unconfined, + Dispatchers.Default, + Executors.newFixedThreadPool(1).asCoroutineDispatcher(), + Executors.newScheduledThreadPool(1).asCoroutineDispatcher(), + ThrowingDispatcher, ThrowingDispatcher2 + ) + + return elements.flatMap { element -> + dispatchers.map { dispatcher -> + arrayOf(element, dispatcher) + } + } + } + } + + @Test + fun testElement() = runTest { + launch(NonCancellable + dispatcher + exceptionHandler + element) {} + checkException() + } + + @Test + fun testNestedElement() = runTest { + launch(NonCancellable + dispatcher + exceptionHandler) { + launch(element) { } + } + checkException() + } + + @Test + fun testNestedDispatcherAndElement() = runTest { + launch(lazyOuterDispatcher.value + NonCancellable + exceptionHandler) { + launch(element + dispatcher) { } + } + checkException() + } + + private fun checkException() { + latch.await(2, TimeUnit.SECONDS) + val e = caught + assertNotNull(e) + // First condition -- failure in context element + val firstCondition = e is CoroutinesInternalError && e.cause is TestException + // Second condition -- failure from isDispatchNeeded (#880) + val secondCondition = e is TestException + assertTrue(firstCondition xor secondCondition) + } +} diff --git a/core/kotlinx-coroutines-core/test/IODispatcherTest.kt b/kotlinx-coroutines-core/jvm/test/IODispatcherTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/IODispatcherTest.kt rename to kotlinx-coroutines-core/jvm/test/IODispatcherTest.kt diff --git a/core/kotlinx-coroutines-core/test/JobActivationStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobActivationStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/JobActivationStressTest.kt rename to kotlinx-coroutines-core/jvm/test/JobActivationStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/JobChildStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobChildStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/JobChildStressTest.kt rename to kotlinx-coroutines-core/jvm/test/JobChildStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/JobDisposeStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/JobDisposeStressTest.kt rename to kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/JobHandlersUpgradeStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/JobHandlersUpgradeStressTest.kt rename to kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/JobStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/JobStressTest.kt rename to kotlinx-coroutines-core/jvm/test/JobStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/JoinStressTest.kt b/kotlinx-coroutines-core/jvm/test/JoinStressTest.kt similarity index 93% rename from core/kotlinx-coroutines-core/test/JoinStressTest.kt rename to kotlinx-coroutines-core/jvm/test/JoinStressTest.kt index b3619aaefa..0d1a7c6c3f 100644 --- a/core/kotlinx-coroutines-core/test/JoinStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/JoinStressTest.kt @@ -6,7 +6,6 @@ package kotlinx.coroutines import org.junit.* import org.junit.Test -import java.io.* import java.util.concurrent.* import kotlin.test.* @@ -71,14 +70,15 @@ class JoinStressTest : TestBase() { exceptionalJob.await() } catch (e: TestException) { 0 - } catch (e: IOException) { + } catch (e: TestException1) { 1 } } val canceller = async(pool + NonCancellable) { barrier.await() - exceptionalJob.cancel(IOException()) + // cast for test purposes only + (exceptionalJob as AbstractCoroutine<*>).cancelInternal(TestException1()) } barrier.await() diff --git a/core/kotlinx-coroutines-core/test/RunBlockingTest.kt b/kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/RunBlockingTest.kt rename to kotlinx-coroutines-core/jvm/test/RunBlockingTest.kt diff --git a/core/kotlinx-coroutines-core/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt similarity index 80% rename from core/kotlinx-coroutines-core/test/TestBase.kt rename to kotlinx-coroutines-core/jvm/test/TestBase.kt index d16d9723fb..6fef760af1 100644 --- a/core/kotlinx-coroutines-core/test/TestBase.kt +++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt @@ -62,13 +62,20 @@ public actual open class TestBase actual constructor() { */ @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun error(message: Any, cause: Throwable? = null): Nothing { - val exception = IllegalStateException(message.toString(), cause) + throw makeError(message, cause) + } + + private fun makeError(message: Any, cause: Throwable? = null): IllegalStateException = + IllegalStateException(message.toString(), cause).also { + setError(it) + } + + private fun setError(exception: Throwable) { error.compareAndSet(null, exception) - throw exception } private fun printError(message: String, cause: Throwable) { - error.compareAndSet(null, cause) + setError(cause) println("$message: $cause") cause.printStackTrace(System.out) println("--- Detected at ---") @@ -118,21 +125,35 @@ public actual open class TestBase actual constructor() { initPoolsBeforeTest() threadsBefore = currentThreads() originalUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler() - Thread.setDefaultUncaughtExceptionHandler({ t, e -> - println("Uncaught exception in thread $t: $e") + Thread.setDefaultUncaughtExceptionHandler { t, e -> + println("Exception in thread $t: $e") // The same message as in default handler e.printStackTrace() uncaughtExceptions.add(e) - }) + } } @After fun onCompletion() { - error.get()?.let { throw it } - check(actionIndex.get() == 0 || finished.get()) { "Expecting that 'finish(...)' was invoked, but it was not" } + // onCompletion should not throw exceptions before it finishes all cleanup, so that other tests always + // start in a clear, restored state + if (actionIndex.get() != 0 && !finished.get()) { + makeError("Expecting that 'finish(...)' was invoked, but it was not") + } + // Shutdown all thread pools shutdownPoolsAfterTest() - checkTestThreads(threadsBefore) + // Check that that are now leftover threads + runCatching { + checkTestThreads(threadsBefore) + }.onFailure { + setError(it) + } + // Restore original uncaught exception handler Thread.setDefaultUncaughtExceptionHandler(originalUncaughtExceptionHandler) - assertTrue(uncaughtExceptions.isEmpty(), "Expected no uncaught exceptions, but got $uncaughtExceptions") + if (uncaughtExceptions.isNotEmpty()) { + makeError("Expected no uncaught exceptions, but got $uncaughtExceptions") + } + // The very last action -- throw error if any was detected + error.get()?.let { throw it } } fun initPoolsBeforeTest() { @@ -180,4 +201,10 @@ public actual open class TestBase actual constructor() { if (exCount < unhandled.size) error("Too few unhandled exceptions $exCount, expected ${unhandled.size}") } + + protected inline fun assertFailsWith(block: () -> Unit): T { + val result = runCatching(block) + assertTrue(result.exceptionOrNull() is T, "Expected ${T::class}, but had $result") + return result.exceptionOrNull()!! as T + } } diff --git a/core/kotlinx-coroutines-core/test/TestBaseTest.kt b/kotlinx-coroutines-core/jvm/test/TestBaseTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/TestBaseTest.kt rename to kotlinx-coroutines-core/jvm/test/TestBaseTest.kt diff --git a/core/kotlinx-coroutines-core/test/TestSecurityManager.kt b/kotlinx-coroutines-core/jvm/test/TestSecurityManager.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/TestSecurityManager.kt rename to kotlinx-coroutines-core/jvm/test/TestSecurityManager.kt diff --git a/core/kotlinx-coroutines-core/test/ThreadContextElementTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/ThreadContextElementTest.kt rename to kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt diff --git a/core/kotlinx-coroutines-core/test/ThreadLocalTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadLocalTest.kt similarity index 88% rename from core/kotlinx-coroutines-core/test/ThreadLocalTest.kt rename to kotlinx-coroutines-core/jvm/test/ThreadLocalTest.kt index 62a340eef4..5d8c3d5c6d 100644 --- a/core/kotlinx-coroutines-core/test/ThreadLocalTest.kt +++ b/kotlinx-coroutines-core/jvm/test/ThreadLocalTest.kt @@ -6,6 +6,7 @@ package kotlinx.coroutines import org.junit.* import org.junit.Test +import java.lang.IllegalStateException import kotlin.test.* @Suppress("RedundantAsync") @@ -22,25 +23,33 @@ class ThreadLocalTest : TestBase() { @Test fun testThreadLocal() = runTest { assertNull(stringThreadLocal.get()) + assertFalse(stringThreadLocal.isPresent()) val deferred = async(Dispatchers.Default + stringThreadLocal.asContextElement("value")) { assertEquals("value", stringThreadLocal.get()) + assertTrue(stringThreadLocal.isPresent()) withContext(executor) { + assertTrue(stringThreadLocal.isPresent()) + assertFailsWith { intThreadLocal.ensurePresent() } assertEquals("value", stringThreadLocal.get()) } + assertTrue(stringThreadLocal.isPresent()) assertEquals("value", stringThreadLocal.get()) } assertNull(stringThreadLocal.get()) deferred.await() assertNull(stringThreadLocal.get()) + assertFalse(stringThreadLocal.isPresent()) } @Test fun testThreadLocalInitialValue() = runTest { intThreadLocal.set(42) + assertFalse(intThreadLocal.isPresent()) val deferred = async(Dispatchers.Default + intThreadLocal.asContextElement(239)) { assertEquals(239, intThreadLocal.get()) withContext(executor) { + intThreadLocal.ensurePresent() assertEquals(239, intThreadLocal.get()) } assertEquals(239, intThreadLocal.get()) @@ -63,6 +72,8 @@ class ThreadLocalTest : TestBase() { withContext(executor) { assertEquals(239, intThreadLocal.get()) assertEquals("pew", stringThreadLocal.get()) + intThreadLocal.ensurePresent() + stringThreadLocal.ensurePresent() } assertEquals(239, intThreadLocal.get()) @@ -129,6 +140,7 @@ class ThreadLocalTest : TestBase() { } deferred.await() + assertFalse(stringThreadLocal.isPresent()) assertEquals("main", stringThreadLocal.get()) } @@ -212,4 +224,10 @@ class ThreadLocalTest : TestBase() { assertNotSame(mainThread, Thread.currentThread()) }.await() } + + @Test + fun testMissingThreadLocal() = runTest { + assertFailsWith { stringThreadLocal.ensurePresent() } + assertFailsWith { intThreadLocal.ensurePresent() } + } } diff --git a/core/kotlinx-coroutines-core/test/Threads.kt b/kotlinx-coroutines-core/jvm/test/Threads.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/Threads.kt rename to kotlinx-coroutines-core/jvm/test/Threads.kt diff --git a/core/kotlinx-coroutines-core/test/UnconfinedConcurrentStressTest.kt b/kotlinx-coroutines-core/jvm/test/UnconfinedConcurrentStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/UnconfinedConcurrentStressTest.kt rename to kotlinx-coroutines-core/jvm/test/UnconfinedConcurrentStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/VirtualTimeSource.kt b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/VirtualTimeSource.kt rename to kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt diff --git a/core/kotlinx-coroutines-core/test/WithDefaultContextTest.kt b/kotlinx-coroutines-core/jvm/test/WithDefaultContextTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/WithDefaultContextTest.kt rename to kotlinx-coroutines-core/jvm/test/WithDefaultContextTest.kt diff --git a/core/kotlinx-coroutines-core/test/WithTimeoutChildDipspatchStressTest.kt b/kotlinx-coroutines-core/jvm/test/WithTimeoutChildDipspatchStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/WithTimeoutChildDipspatchStressTest.kt rename to kotlinx-coroutines-core/jvm/test/WithTimeoutChildDipspatchStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/WithTimeoutOrNullJvmTest.kt b/kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullJvmTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/WithTimeoutOrNullJvmTest.kt rename to kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullJvmTest.kt diff --git a/core/kotlinx-coroutines-core/test/WithTimeoutOrNullThreadDispatchTest.kt b/kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullThreadDispatchTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/WithTimeoutOrNullThreadDispatchTest.kt rename to kotlinx-coroutines-core/jvm/test/WithTimeoutOrNullThreadDispatchTest.kt diff --git a/core/kotlinx-coroutines-core/test/WithTimeoutThreadDispatchTest.kt b/kotlinx-coroutines-core/jvm/test/WithTimeoutThreadDispatchTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/WithTimeoutThreadDispatchTest.kt rename to kotlinx-coroutines-core/jvm/test/WithTimeoutThreadDispatchTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/ActorLazyTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ActorLazyTest.kt similarity index 85% rename from core/kotlinx-coroutines-core/test/channels/ActorLazyTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/ActorLazyTest.kt index 9a04440695..1ec96ee5ab 100644 --- a/core/kotlinx-coroutines-core/test/channels/ActorLazyTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/ActorLazyTest.kt @@ -61,4 +61,22 @@ class ActorLazyTest : TestBase() { assertThat(actor.isClosedForSend, IsEqual(true)) finish(6) } + + @Test + fun testCloseFreshActor() = runTest { + val job = launch { + expect(2) + val actor = actor(start = CoroutineStart.LAZY) { + expect(3) + for (i in channel) { } + expect(4) + } + + actor.close() + } + + expect(1) + job.join() + finish(5) + } } \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/test/channels/ActorTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ActorTest.kt similarity index 93% rename from core/kotlinx-coroutines-core/test/channels/ActorTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/ActorTest.kt index db08ddbbf7..7be7983203 100644 --- a/core/kotlinx-coroutines-core/test/channels/ActorTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/ActorTest.kt @@ -169,4 +169,16 @@ class ActorTest(private val capacity: Int) : TestBase() { parent.join() finish(2) } + + @Test + fun testCloseFreshActor() = runTest { + for (start in CoroutineStart.values()) { + val job = launch { + val actor = actor(start = start) { for (i in channel) {} } + actor.close() + } + + job.join() + } + } } diff --git a/core/kotlinx-coroutines-core/test/channels/ArrayChannelStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ArrayChannelStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/ArrayChannelStressTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/ArrayChannelStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/BroadcastChannelMultiReceiveStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelMultiReceiveStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/BroadcastChannelMultiReceiveStressTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelMultiReceiveStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/BroadcastChannelSubStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelSubStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/BroadcastChannelSubStressTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelSubStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/ChannelAtomicCancelStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelAtomicCancelStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/ChannelAtomicCancelStressTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/ChannelAtomicCancelStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/ChannelSendReceiveStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/ChannelSendReceiveStressTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/ChannelsConsumeTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelsConsumeTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/ChannelsConsumeTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/ChannelsConsumeTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/ChannelsJvmTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/ChannelsJvmTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/ChannelsJvmTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/ConflatedChannelCloseStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ConflatedChannelCloseStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/ConflatedChannelCloseStressTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/ConflatedChannelCloseStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/DoubleChannelCloseStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/DoubleChannelCloseStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/DoubleChannelCloseStressTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/DoubleChannelCloseStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/InvokeOnCloseStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/InvokeOnCloseStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/InvokeOnCloseStressTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/InvokeOnCloseStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/ProduceConsumeJvmTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ProduceConsumeJvmTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/ProduceConsumeJvmTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/ProduceConsumeJvmTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/RandevouzChannelStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/RandevouzChannelStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/RandevouzChannelStressTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/RandevouzChannelStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/SendReceiveJvmStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/SendReceiveJvmStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/SendReceiveJvmStressTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/SendReceiveJvmStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/SimpleSendReceiveJvmTest.kt b/kotlinx-coroutines-core/jvm/test/channels/SimpleSendReceiveJvmTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/SimpleSendReceiveJvmTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/SimpleSendReceiveJvmTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/TickerChannelCommonTest.kt b/kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/TickerChannelCommonTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt diff --git a/core/kotlinx-coroutines-core/test/channels/TickerChannelTest.kt b/kotlinx-coroutines-core/jvm/test/channels/TickerChannelTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/channels/TickerChannelTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/TickerChannelTest.kt diff --git a/core/kotlinx-coroutines-core/test/exceptions/CoroutineExceptionHandlerJvmTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/CoroutineExceptionHandlerJvmTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/exceptions/CoroutineExceptionHandlerJvmTest.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/CoroutineExceptionHandlerJvmTest.kt diff --git a/core/kotlinx-coroutines-core/test/exceptions/Exceptions.kt b/kotlinx-coroutines-core/jvm/test/exceptions/Exceptions.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/exceptions/Exceptions.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/Exceptions.kt diff --git a/core/kotlinx-coroutines-core/test/exceptions/JobBasicCancellationTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/JobBasicCancellationTest.kt similarity index 95% rename from core/kotlinx-coroutines-core/test/exceptions/JobBasicCancellationTest.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/JobBasicCancellationTest.kt index 9d347b9423..28d85fe341 100644 --- a/core/kotlinx-coroutines-core/test/exceptions/JobBasicCancellationTest.kt +++ b/kotlinx-coroutines-core/jvm/test/exceptions/JobBasicCancellationTest.kt @@ -149,8 +149,8 @@ class JobBasicCancellationTest : TestBase() { @Test fun testConsecutiveCancellation() { val deferred = CompletableDeferred() - assertTrue(deferred.cancel(IndexOutOfBoundsException())) - assertFalse(deferred.cancel(AssertionError())) // second cancelled is too late + assertTrue(deferred.completeExceptionally(IndexOutOfBoundsException())) + assertFalse(deferred.completeExceptionally(AssertionError())) // second is too late val cause = deferred.getCancellationException().cause!! assertTrue(cause is IndexOutOfBoundsException) assertNull(cause.cause) diff --git a/core/kotlinx-coroutines-core/test/exceptions/JobExceptionHandlingTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionHandlingTest.kt similarity index 88% rename from core/kotlinx-coroutines-core/test/exceptions/JobExceptionHandlingTest.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionHandlingTest.kt index bcc90b57c9..746909a61e 100644 --- a/core/kotlinx-coroutines-core/test/exceptions/JobExceptionHandlingTest.kt +++ b/kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionHandlingTest.kt @@ -44,11 +44,12 @@ class JobExceptionHandlingTest : TestBase() { expect(1) yield() - deferred.cancel(IOException()) + deferred.cancel(TestCancellationException("TEST")) try { deferred.await() expectUnreached() - } catch (e: IOException) { + } catch (e: TestCancellationException) { + assertEquals("TEST", e.message) assertTrue(e.suppressed.isEmpty()) finish(3) } @@ -64,7 +65,7 @@ class JobExceptionHandlingTest : TestBase() { expect(1) yield() - parent.cancel(IOException()) + parent.completeExceptionally(IOException()) try { deferred.await() expectUnreached() @@ -100,35 +101,6 @@ class JobExceptionHandlingTest : TestBase() { checkException(exception) } - @Test - fun testConsecutiveCancellation() { - /* - * Root parent: JobImpl() - * Child: throws IOException - * Launcher: cancels child with AE and then cancels it with NPE - * Result: AE with suppressed NPE and IOE - */ - val exception = runBlock { - val job = Job() - val child = launch(job, start = ATOMIC) { - expect(2) - throw IOException() - } - - expect(1) - child.cancel(ArithmeticException()) - child.cancel(NullPointerException()) - job.join() - finish(3) - } - - assertTrue(exception is ArithmeticException) - val suppressed = exception.suppressed - assertEquals(2, suppressed.size) - checkException(suppressed[0]) - checkException(suppressed[1]) - } - @Test fun testExceptionOnChildCancellation() { /* @@ -216,7 +188,7 @@ class JobExceptionHandlingTest : TestBase() { yield() expect(4) - job.cancel(IOException()) + job.completeExceptionally(IOException()) } expect(1) diff --git a/core/kotlinx-coroutines-core/test/exceptions/JobExceptionsStressTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionsStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/exceptions/JobExceptionsStressTest.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/JobExceptionsStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/exceptions/JobNestedExceptionsTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/JobNestedExceptionsTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/exceptions/JobNestedExceptionsTest.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/JobNestedExceptionsTest.kt diff --git a/core/kotlinx-coroutines-core/test/exceptions/ProduceExceptionsTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/ProduceExceptionsTest.kt similarity index 89% rename from core/kotlinx-coroutines-core/test/exceptions/ProduceExceptionsTest.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/ProduceExceptionsTest.kt index c074e87c9d..b4df58ac34 100644 --- a/core/kotlinx-coroutines-core/test/exceptions/ProduceExceptionsTest.kt +++ b/kotlinx-coroutines-core/jvm/test/exceptions/ProduceExceptionsTest.kt @@ -51,17 +51,16 @@ class ProduceExceptionsTest : TestBase() { @Test fun testSuppressedException() = runTest { - val produce = produce(Job()) { + val produce = produce(NonCancellable) { launch(start = CoroutineStart.ATOMIC) { - throw TestException() + throw TestException() // child coroutine fails } try { delay(Long.MAX_VALUE) } finally { - throw TestException2() + throw TestException2() // but parent throws another exception while cleaning up } } - try { produce.receive() expectUnreached() @@ -99,11 +98,11 @@ class ProduceExceptionsTest : TestBase() { var channel: ReceiveChannel? = null channel = produce(NonCancellable) { expect(2) - channel!!.cancel(TestException()) + channel!!.cancel(TestCancellationException()) try { send(1) // Not a ClosedForSendException - } catch (e: TestException) { + } catch (e: TestCancellationException) { expect(3) throw e } @@ -113,7 +112,7 @@ class ProduceExceptionsTest : TestBase() { yield() try { channel.receive() - } catch (e: TestException) { + } catch (e: TestCancellationException) { assertTrue(e.suppressed.isEmpty()) finish(4) } @@ -148,7 +147,7 @@ class ProduceExceptionsTest : TestBase() { val job = Job() val channel = produce(job) { expect(2) - job.cancel(TestException2()) + job.completeExceptionally(TestException2()) try { send(1) } catch (e: CancellationException) { // Not a TestException2 diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt new file mode 100644 index 0000000000..70336659e8 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.exceptions + +import kotlinx.coroutines.* +import org.junit.Test +import kotlin.test.* + +@Suppress("UNREACHABLE_CODE", "UNUSED", "UNUSED_PARAMETER") +class StackTraceRecoveryCustomExceptionsTest : TestBase() { + + internal class NonCopyable(val customData: Int) : Throwable() { + // Bait + public constructor(cause: Throwable) : this(42) + } + + internal class Copyable(val customData: Int) : Throwable(), CopyableThrowable { + // Bait + public constructor(cause: Throwable) : this(42) + + override fun createCopy(): Copyable { + val copy = Copyable(customData) + copy.initCause(this) + return copy + } + } + + @Test + fun testStackTraceNotRecovered() = runTest { + try { + withContext(wrapperDispatcher(coroutineContext)) { + throw NonCopyable(239) + } + expectUnreached() + } catch (e: NonCopyable) { + assertEquals(239, e.customData) + assertNull(e.cause) + } + } + + @Test + fun testStackTraceRecovered() = runTest { + try { + withContext(wrapperDispatcher(coroutineContext)) { + throw Copyable(239) + } + expectUnreached() + } catch (e: Copyable) { + assertEquals(239, e.customData) + val cause = e.cause + assertTrue(cause is Copyable) + assertEquals(239, cause.customData) + } + } + + internal class WithDefault(message: String = "default") : Exception(message) + + @Test + fun testStackTraceRecoveredWithCustomMessage() = runTest { + try { + withContext(wrapperDispatcher(coroutineContext)) { + throw WithDefault("custom") + } + expectUnreached() + } catch (e: WithDefault) { + assertEquals("custom", e.message) + val cause = e.cause + assertTrue(cause is WithDefault) + assertEquals("custom", cause.message) + } + } +} diff --git a/core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryNestedChannelsTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedChannelsTest.kt similarity index 83% rename from core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryNestedChannelsTest.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedChannelsTest.kt index d922edef0c..748f0c1697 100644 --- a/core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryNestedChannelsTest.kt +++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedChannelsTest.kt @@ -111,7 +111,7 @@ class StackTraceRecoveryNestedChannelsTest : TestBase() { sendFromScope() } catch (e: Exception) { verifyStackTrace(e, - "kotlinx.coroutines.RecoverableTestException\n" + + "kotlinx.coroutines.RecoverableTestCancellationException\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testSendFromScope\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:118)\n" + "\t(Coroutine boundary)\n" + "\tat kotlinx.coroutines.channels.AbstractSendChannel.offer(AbstractChannel.kt:180)\n" + @@ -120,7 +120,7 @@ class StackTraceRecoveryNestedChannelsTest : TestBase() { "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$sendWithContext\$2.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:19)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$sendFromScope\$2.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:29)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testSendFromScope\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:109)\n" + - "Caused by: kotlinx.coroutines.RecoverableTestException\n" + + "Caused by: kotlinx.coroutines.RecoverableTestCancellationException\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testSendFromScope\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:118)\n" + "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)") } @@ -129,8 +129,37 @@ class StackTraceRecoveryNestedChannelsTest : TestBase() { yield() expect(2) // Cancel is an analogue of `produce` failure, just a shorthand - channel.cancel(RecoverableTestException()) + channel.cancel(RecoverableTestCancellationException()) finish(3) deferred.await() } + + // See https://github.com/Kotlin/kotlinx.coroutines/issues/950 + @Test + fun testCancelledOffer() = runTest { + expect(1) + val job = Job() + val actor = actor(job, Channel.UNLIMITED) { + consumeEach { + expectUnreached() // is cancelled before offer + } + } + job.cancel() + try { + actor.offer(1) + } catch (e: Exception) { + verifyStackTrace(e, + "kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@3af42ad0\n" + + "\t(Coroutine boundary)\n" + + "\tat kotlinx.coroutines.channels.AbstractSendChannel.offer(AbstractChannel.kt:186)\n" + + "\tat kotlinx.coroutines.channels.ChannelCoroutine.offer(ChannelCoroutine.kt)\n" + + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testCancelledOffer\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:150)\n" + + "Caused by: kotlinx.coroutines.JobCancellationException: Job was cancelled; job=JobImpl{Cancelling}@3af42ad0\n", + // ... java.lang.* stuff and JobSupport.* snipped here ... + "\tat kotlinx.coroutines.Job\$DefaultImpls.cancel\$default(Job.kt:164)\n" + + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedChannelsTest\$testCancelledOffer\$1.invokeSuspend(StackTraceRecoveryNestedChannelsTest.kt:148)" + ) + finish(2) + } + } } diff --git a/core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryNestedScopesTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryNestedScopesTest.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt diff --git a/core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryNestedTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryNestedTest.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedTest.kt diff --git a/core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt similarity index 81% rename from core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryTest.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt index f96da49e86..18fed01a2f 100644 --- a/core/kotlinx-coroutines-core/test/exceptions/StackTraceRecoveryTest.kt +++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt @@ -6,8 +6,8 @@ package kotlinx.coroutines.exceptions import kotlinx.coroutines.* import kotlinx.coroutines.channels.* +import kotlinx.coroutines.selects.* import org.junit.Test -import java.io.* import java.util.concurrent.* import kotlin.test.* @@ -180,6 +180,7 @@ class StackTraceRecoveryTest : TestBase() { "\t(Coroutine boundary)\n" + "\tat kotlinx.coroutines.DeferredCoroutine.await\$suspendImpl(Builders.common.kt:99)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.innerMethod(StackTraceRecoveryTest.kt:158)\n" + + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerScopedMethod\$2\$1.invokeSuspend(StackTraceRecoveryTest.kt:193)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerScopedMethod\$2.invokeSuspend(StackTraceRecoveryTest.kt:151)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCoroutineScope\$1.invokeSuspend(StackTraceRecoveryTest.kt:141)\n", "Caused by: kotlinx.coroutines.RecoverableTestException\n" + @@ -188,8 +189,66 @@ class StackTraceRecoveryTest : TestBase() { deferred.join() } + public class TrickyException() : Throwable() { + // To be sure ctor is never invoked + @Suppress("UNUSED", "UNUSED_PARAMETER") + private constructor(message: String, cause: Throwable): this() { + error("Should never be called") + } + + override fun initCause(cause: Throwable?): Throwable { + error("Can't call initCause") + } + } + + @Test + fun testThrowingInitCause() = runTest { + val deferred = async(NonCancellable) { + expect(2) + throw TrickyException() + } + + try { + expect(1) + deferred.await() + } catch (e: TrickyException) { + assertNull(e.cause) + finish(3) + } + } + private suspend fun outerScopedMethod(deferred: Deferred, vararg traces: String) = coroutineScope { - innerMethod(deferred, *traces) + supervisorScope { + innerMethod(deferred, *traces) + assertTrue(true) + } assertTrue(true) } + + @Test + fun testSelect() = runTest { + expect(1) + val result = kotlin.runCatching { doSelect() } + expect(3) + verifyStackTrace(result.exceptionOrNull()!!, + "kotlinx.coroutines.RecoverableTestException\n" + + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$doSelect\$\$inlined\$select\$lambda\$1.invokeSuspend(StackTraceRecoveryTest.kt:211)\n" + + "\t(Coroutine boundary)\n" + + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testSelect\$1.invokeSuspend(StackTraceRecoveryTest.kt:199)\n" + + "Caused by: kotlinx.coroutines.RecoverableTestException\n" + + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$doSelect\$\$inlined\$select\$lambda\$1.invokeSuspend(StackTraceRecoveryTest.kt:211)\n" + + "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)") + finish(4) + } + + private suspend fun doSelect(): Int { + val job = CompletableDeferred(Unit) + return select { + job.onJoin { + yield() + expect(2) + throw RecoverableTestException() + } + } + } } diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/Stacktraces.kt b/kotlinx-coroutines-core/jvm/test/exceptions/Stacktraces.kt new file mode 100644 index 0000000000..15884332b4 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/exceptions/Stacktraces.kt @@ -0,0 +1,50 @@ +package kotlinx.coroutines.exceptions + +import java.io.* +import kotlin.test.* + +public fun verifyStackTrace(e: Throwable, vararg traces: String) { + val stacktrace = toStackTrace(e) + val normalizedActual = stacktrace.normalizeStackTrace() + traces.forEach { + val normalizedExpected = it.normalizeStackTrace() + if (!normalizedActual.contains(normalizedExpected)) { + // A more readable error message would be produced by assertEquals + assertEquals(normalizedExpected, normalizedActual, "Actual trace does not contain expected one") + } + } + // Check "Caused by" counts + val causes = stacktrace.count("Caused by") + assertNotEquals(0, causes) + assertEquals(traces.map { it.count("Caused by") }.sum(), causes) +} + +public fun toStackTrace(t: Throwable): String { + val sw = StringWriter() as Writer + t.printStackTrace(PrintWriter(sw)) + return sw.toString() +} + +public fun String.normalizeStackTrace(): String = + applyBackspace() + .replace(Regex(":[0-9]+"), "") // remove line numbers + .replace("kotlinx_coroutines_core_main", "") // yay source sets + .replace("kotlinx_coroutines_core", "") + .replace(Regex("@[0-9a-f]+"), "") // remove hex addresses in debug toStrings + .lines().joinToString("\n") // normalize line separators + +public fun String.applyBackspace(): String { + val array = toCharArray() + val stack = CharArray(array.size) + var stackSize = -1 + for (c in array) { + if (c != '\b') { + stack[++stackSize] = c + } else { + --stackSize + } + } + return String(stack, 0, stackSize) +} + +public fun String.count(substring: String): Int = split(substring).size - 1 \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/test/exceptions/SuppressionTests.kt b/kotlinx-coroutines-core/jvm/test/exceptions/SuppressionTests.kt similarity index 69% rename from core/kotlinx-coroutines-core/test/exceptions/SuppressionTests.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/SuppressionTests.kt index e5f96f78d1..6034fccbca 100644 --- a/core/kotlinx-coroutines-core/test/exceptions/SuppressionTests.kt +++ b/kotlinx-coroutines-core/jvm/test/exceptions/SuppressionTests.kt @@ -12,25 +12,6 @@ import kotlin.test.* @Suppress("DEPRECATION") class SuppressionTests : TestBase() { - - @Test - fun testCancellationTransparency() = runTest { - val deferred = async(NonCancellable, start = CoroutineStart.ATOMIC) { - expect(2) - throw ArithmeticException() - } - - expect(1) - deferred.cancel(TestException("Message")) - - try { - deferred.await() - } catch (e: TestException) { - checkException(e.suppressed[0]) - finish(3) - } - } - @Test fun testNotificationsWithException() = runTest { expect(1) @@ -40,7 +21,7 @@ class SuppressionTests : TestBase() { expect(3) } - override fun onCancellation(cause: Throwable?) { + override fun onCancelling(cause: Throwable?) { assertTrue(cause is ArithmeticException) assertTrue(cause.suppressed.isEmpty()) expect(5) @@ -50,10 +31,10 @@ class SuppressionTests : TestBase() { expectUnreached() } - override fun onCompletedExceptionally(exception: Throwable) { - assertTrue(exception is ArithmeticException) - checkException(exception.suppressed[0]) - expect(9) + override fun onCancelled(cause: Throwable, handled: Boolean) { + assertTrue(cause is ArithmeticException) + checkException(cause.suppressed[0]) + expect(8) } } @@ -66,13 +47,13 @@ class SuppressionTests : TestBase() { coroutine.invokeOnCompletion { assertTrue(it is ArithmeticException) checkException(it.suppressed[0]) - expect(8) + expect(9) } expect(2) coroutine.start() expect(4) - coroutine.cancel(ArithmeticException()) + coroutine.cancelInternal(ArithmeticException()) expect(7) coroutine.resumeWithException(IOException()) finish(10) @@ -88,7 +69,7 @@ class SuppressionTests : TestBase() { } launch { - val exception = RecoverableTestException() + val exception = RecoverableTestCancellationException() channel.cancel(exception) throw exception } diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/WithContextCancellationStressTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/WithContextCancellationStressTest.kt new file mode 100644 index 0000000000..2995466390 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/exceptions/WithContextCancellationStressTest.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.exceptions + +import kotlinx.coroutines.* +import org.junit.* +import org.junit.Test +import java.util.concurrent.* +import kotlin.coroutines.* +import kotlin.test.* + +class WithContextCancellationStressTest : TestBase() { + + private val iterations = 15_000 * stressTestMultiplier + private val pool = newFixedThreadPoolContext(3, "WithContextCancellationStressTest") + + @After + fun tearDown() { + pool.close() + } + + @Test + @Suppress("DEPRECATION") + fun testConcurrentFailure() = runBlocking { + var eCnt = 0 + var e1Cnt = 0 + var e2Cnt = 0 + + repeat(iterations) { + val barrier = CyclicBarrier(4) + val ctx = pool + NonCancellable + var e1 = false + var e2 = false + val jobWithContext = async(ctx) { + withContext(wrapperDispatcher(coroutineContext)) { + launch { + barrier.await() + e1 = true + throw TestException1() + } + + launch { + barrier.await() + e2 = true + throw TestException2() + } + + barrier.await() + throw TestException() + } + } + + barrier.await() + + try { + jobWithContext.await() + } catch (e: Throwable) { + when (e) { + is TestException -> { + eCnt++ + e.checkSuppressed(e1 = e1, e2 = e2) + } + is TestException1 -> { + e1Cnt++ + e.checkSuppressed(ex = true, e2 = e2) + } + is TestException2 -> { + e2Cnt++ + e.checkSuppressed(ex = true, e1 = e1) + } + else -> error("Unexpected exception $e") + } + } + } + + require(eCnt > 0) { "At least one TestException expected" } + require(e1Cnt > 0) { "At least one TestException1 expected" } + require(e2Cnt > 0) { "At least one TestException2 expected" } + } + + private fun wrapperDispatcher(context: CoroutineContext): CoroutineContext { + val dispatcher = context[ContinuationInterceptor] as CoroutineDispatcher + return object : CoroutineDispatcher() { + override fun dispatch(context: CoroutineContext, block: Runnable) { + dispatcher.dispatch(context, block) + } + } + } + + private fun Throwable.checkSuppressed( + ex: Boolean = false, + e1: Boolean = false, + e2: Boolean = false + ) { + val suppressed: Array = suppressed + if (ex) { + assertTrue(suppressed.any { it is TestException }, "TestException should be present: $this") + } + if (e1) { + assertTrue(suppressed.any { it is TestException1 }, "TestException1 should be present: $this") + } + if (e2) { + assertTrue(suppressed.any { it is TestException2 }, "TestException2 should be present: $this") + } + } +} diff --git a/core/kotlinx-coroutines-core/test/exceptions/WithContextExceptionHandlingTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/WithContextExceptionHandlingTest.kt similarity index 82% rename from core/kotlinx-coroutines-core/test/exceptions/WithContextExceptionHandlingTest.kt rename to kotlinx-coroutines-core/jvm/test/exceptions/WithContextExceptionHandlingTest.kt index 19448b7bd7..124e5569ab 100644 --- a/core/kotlinx-coroutines-core/test/exceptions/WithContextExceptionHandlingTest.kt +++ b/kotlinx-coroutines-core/jvm/test/exceptions/WithContextExceptionHandlingTest.kt @@ -8,7 +8,6 @@ import kotlinx.coroutines.* import org.junit.Test import org.junit.runner.* import org.junit.runners.* -import java.io.* import kotlin.coroutines.* import kotlin.test.* @@ -41,30 +40,29 @@ class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() { @Test fun testCancellationWithException() = runTest { /* - * context cancelled with TE + * context cancelled with TCE * block itself throws TE2 - * Result: TE with suppressed TE2 + * Result: TE (CancellationException is always ignored) */ - val cancellationCause = TestException() + val cancellationCause = TestCancellationException() runCancellation(cancellationCause, TestException2()) { e -> - assertTrue(e is TestException) + assertTrue(e is TestException2) assertNull(e.cause) val suppressed = e.suppressed - assertEquals(suppressed.size, 1) - assertTrue(suppressed[0] is TestException2) + assertTrue(suppressed.isEmpty()) } } @Test fun testSameException() = runTest { /* - * context cancelled with TE - * block itself throws the same TE - * Result: TE + * context cancelled with TCE + * block itself throws the same TCE + * Result: TCE */ - val cancellationCause = TestException() + val cancellationCause = TestCancellationException() runCancellation(cancellationCause, cancellationCause) { e -> - assertTrue(e is TestException) + assertTrue(e is TestCancellationException) assertNull(e.cause) val suppressed = e.suppressed assertTrue(suppressed.isEmpty()) @@ -74,11 +72,11 @@ class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() { @Test fun testSameCancellation() = runTest { /* - * context cancelled with CancellationException - * block itself throws the same CE - * Result: CE + * context cancelled with TestCancellationException + * block itself throws the same TCE + * Result: TCE */ - val cancellationCause = CancellationException() + val cancellationCause = TestCancellationException() runCancellation(cancellationCause, cancellationCause) { e -> assertSame(e, cancellationCause) assertNull(e.cause) @@ -107,11 +105,11 @@ class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() { @Test fun testConflictingCancellation() = runTest { /* - * context cancelled with TE + * context cancelled with TCE * block itself throws CE(TE) * Result: TE (because cancellation exception is always ignored and not handled) */ - val cancellationCause = TestException() + val cancellationCause = TestCancellationException() val thrown = CancellationException() thrown.initCause(TestException()) runCancellation(cancellationCause, thrown) { e -> @@ -127,7 +125,7 @@ class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() { * block itself throws CE * Result: TE */ - val cancellationCause = TestException() + val cancellationCause = TestCancellationException() val thrown = CancellationException() runCancellation(cancellationCause, thrown) { e -> assertSame(cancellationCause, e) @@ -139,12 +137,12 @@ class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() { @Test fun testConflictingCancellation3() = runTest { /* - * context cancelled with CE - * block itself throws CE - * Result: CE + * context cancelled with TCE + * block itself throws TCE + * Result: TCE */ - val cancellationCause = CancellationException() - val thrown = CancellationException() + val cancellationCause = TestCancellationException() + val thrown = TestCancellationException() runCancellation(cancellationCause, thrown) { e -> assertSame(cancellationCause, e) assertNull(e.cause) @@ -154,7 +152,7 @@ class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() { @Test fun testThrowingCancellation() = runTest { - val thrown = CancellationException() + val thrown = TestCancellationException() runThrowing(thrown) { e -> assertSame(thrown, e) } @@ -163,7 +161,7 @@ class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() { @Test fun testThrowingCancellationWithCause() = runTest { // Exception are never unwrapped, so if CE(TE) is thrown then it is the cancellation cause - val thrown = CancellationException() + val thrown = TestCancellationException() thrown.initCause(TestException()) runThrowing(thrown) { e -> assertSame(thrown, e) @@ -173,14 +171,16 @@ class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() { @Test fun testCancel() = runTest { runOnlyCancellation(null) { e -> - assertNull(e.cause) + val cause = e.cause as JobCancellationException // shall be recovered JCE + assertNull(cause.cause) assertTrue(e.suppressed.isEmpty()) + assertTrue(cause.suppressed.isEmpty()) } } @Test fun testCancelWithCause() = runTest { - val cause = TestException() + val cause = TestCancellationException() runOnlyCancellation(cause) { e -> assertSame(cause, e) assertTrue(e.suppressed.isEmpty()) @@ -189,7 +189,7 @@ class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() { @Test fun testCancelWithCancellationException() = runTest { - val cause = CancellationException() + val cause = TestCancellationException() runThrowing(cause) { e -> assertSame(cause, e) assertNull(e.cause) @@ -207,7 +207,7 @@ class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() { } private suspend fun runCancellation( - cancellationCause: Throwable?, + cancellationCause: CancellationException?, thrownException: Throwable, exceptionChecker: (Throwable) -> Unit ) { @@ -260,7 +260,7 @@ class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() { } private suspend fun runOnlyCancellation( - cancellationCause: Throwable?, + cancellationCause: CancellationException?, exceptionChecker: (Throwable) -> Unit ) { expect(1) diff --git a/core/kotlinx-coroutines-core/test/guide/example-basic-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-basic-01.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-basic-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-basic-02.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-basic-02b.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-02b.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-basic-02b.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-basic-02b.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-basic-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-basic-03.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-basic-03s.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-03s.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-basic-03s.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-basic-03s.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-basic-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-basic-04.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-basic-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-basic-05.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-basic-05s.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-05s.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-basic-05s.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-basic-05s.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-basic-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-basic-06.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-basic-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-basic-07.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-basic-07.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-cancel-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-cancel-01.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-cancel-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-cancel-02.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-cancel-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-cancel-03.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-cancel-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-cancel-04.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-cancel-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-cancel-05.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-cancel-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-cancel-06.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-cancel-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-cancel-07.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-channel-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-channel-01.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-channel-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-channel-02.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-channel-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-channel-03.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-channel-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-channel-04.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-channel-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-channel-05.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-channel-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-channel-06.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-channel-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-channel-07.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-channel-08.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-channel-08.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-channel-09.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-channel-09.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-channel-10.kt b/kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-channel-10.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-compose-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-compose-01.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-compose-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-compose-02.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-compose-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-compose-03.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-compose-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-compose-04.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-compose-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt similarity index 94% rename from core/kotlinx-coroutines-core/test/guide/example-compose-05.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt index 07c0e97d9e..3e73d2c6e6 100644 --- a/core/kotlinx-coroutines-core/test/guide/example-compose-05.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt @@ -14,13 +14,13 @@ fun main() = runBlocking { println("The answer is ${concurrentSum()}") } println("Completed in $time ms") -//sampleEnd +//sampleEnd } suspend fun concurrentSum(): Int = coroutineScope { val one = async { doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } - one.await() + two.await() + one.await() + two.await() } suspend fun doSomethingUsefulOne(): Int { diff --git a/core/kotlinx-coroutines-core/test/guide/example-compose-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt similarity index 96% rename from core/kotlinx-coroutines-core/test/guide/example-compose-06.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt index 6fc92a2be1..63468421a9 100644 --- a/core/kotlinx-coroutines-core/test/guide/example-compose-06.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt @@ -28,5 +28,5 @@ suspend fun failedConcurrentSum(): Int = coroutineScope { println("Second child throws an exception") throw ArithmeticException() } - one.await() + two.await() + one.await() + two.await() } diff --git a/core/kotlinx-coroutines-core/test/guide/example-context-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-context-01.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-context-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-context-02.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-context-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-context-03.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-context-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-context-04.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-context-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-context-05.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-context-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-context-06.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-context-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-context-07.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-context-08.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-context-08.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-context-09.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-context-09.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-context-10.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt similarity index 76% rename from core/kotlinx-coroutines-core/test/guide/example-context-10.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt index 61b66c4a39..5a7b3d5831 100644 --- a/core/kotlinx-coroutines-core/test/guide/example-context-10.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt @@ -8,23 +8,13 @@ package kotlinx.coroutines.guide.context10 import kotlin.coroutines.* import kotlinx.coroutines.* -class Activity : CoroutineScope { - lateinit var job: Job - - fun create() { - job = Job() - } +class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) { fun destroy() { - job.cancel() + cancel() // Extension on CoroutineScope } // to be continued ... - // class Activity continues - override val coroutineContext: CoroutineContext - get() = Dispatchers.Default + job - // to be continued ... - // class Activity continues fun doSomething() { // launch ten coroutines for a demo, each working for a different time @@ -40,7 +30,6 @@ class Activity : CoroutineScope { fun main() = runBlocking { //sampleStart val activity = Activity() - activity.create() // create an activity activity.doSomething() // run test function println("Launched coroutines") delay(500L) // delay for half a second diff --git a/core/kotlinx-coroutines-core/test/guide/example-context-11.kt b/kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt similarity index 88% rename from core/kotlinx-coroutines-core/test/guide/example-context-11.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt index 8de958e473..1945495cb8 100644 --- a/core/kotlinx-coroutines-core/test/guide/example-context-11.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt @@ -14,7 +14,7 @@ fun main() = runBlocking { threadLocal.set("main") println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = "launch")) { - println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") + println("Launch start, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") yield() println("After yield, current thread: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'") } diff --git a/core/kotlinx-coroutines-core/test/guide/example-exceptions-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-exceptions-01.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-exceptions-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-exceptions-02.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-exceptions-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-exceptions-03.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-exceptions-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-exceptions-04.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-exceptions-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-exceptions-05.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-exceptions-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-exceptions-06.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-select-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-select-01.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-select-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-select-02.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-select-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-select-03.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-select-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-select-04.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-select-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-select-05.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-supervision-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-supervision-01.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-supervision-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-supervision-02.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-supervision-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-supervision-03.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-sync-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-sync-01.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-sync-01b.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-01b.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-sync-01b.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-sync-01b.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-sync-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-sync-02.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-sync-03.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-sync-03.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-sync-04.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-sync-04.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-sync-05.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-sync-05.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-sync-06.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-sync-06.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt diff --git a/core/kotlinx-coroutines-core/test/guide/example-sync-07.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/example-sync-07.kt rename to kotlinx-coroutines-core/jvm/test/guide/example-sync-07.kt diff --git a/core/kotlinx-coroutines-core/test/guide/test/BasicsGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/test/BasicsGuideTest.kt rename to kotlinx-coroutines-core/jvm/test/guide/test/BasicsGuideTest.kt diff --git a/core/kotlinx-coroutines-core/test/guide/test/CancellationTimeOutsGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/CancellationTimeOutsGuideTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/test/CancellationTimeOutsGuideTest.kt rename to kotlinx-coroutines-core/jvm/test/guide/test/CancellationTimeOutsGuideTest.kt diff --git a/core/kotlinx-coroutines-core/test/guide/test/ChannelsGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/ChannelsGuideTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/test/ChannelsGuideTest.kt rename to kotlinx-coroutines-core/jvm/test/guide/test/ChannelsGuideTest.kt diff --git a/core/kotlinx-coroutines-core/test/guide/test/ComposingGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/ComposingGuideTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/test/ComposingGuideTest.kt rename to kotlinx-coroutines-core/jvm/test/guide/test/ComposingGuideTest.kt diff --git a/core/kotlinx-coroutines-core/test/guide/test/DispatcherGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/test/DispatcherGuideTest.kt rename to kotlinx-coroutines-core/jvm/test/guide/test/DispatcherGuideTest.kt diff --git a/core/kotlinx-coroutines-core/test/guide/test/ExceptionsGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/ExceptionsGuideTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/test/ExceptionsGuideTest.kt rename to kotlinx-coroutines-core/jvm/test/guide/test/ExceptionsGuideTest.kt diff --git a/core/kotlinx-coroutines-core/test/guide/test/GuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/GuideTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/test/GuideTest.kt rename to kotlinx-coroutines-core/jvm/test/guide/test/GuideTest.kt diff --git a/core/kotlinx-coroutines-core/test/guide/test/SelectGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/SelectGuideTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/test/SelectGuideTest.kt rename to kotlinx-coroutines-core/jvm/test/guide/test/SelectGuideTest.kt diff --git a/core/kotlinx-coroutines-core/test/guide/test/SharedStateGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/SharedStateGuideTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/test/SharedStateGuideTest.kt rename to kotlinx-coroutines-core/jvm/test/guide/test/SharedStateGuideTest.kt diff --git a/core/kotlinx-coroutines-core/test/guide/test/TestUtil.kt b/kotlinx-coroutines-core/jvm/test/guide/test/TestUtil.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/guide/test/TestUtil.kt rename to kotlinx-coroutines-core/jvm/test/guide/test/TestUtil.kt diff --git a/core/kotlinx-coroutines-core/test/internal/LockFreeLinkedListAtomicStressLFTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicStressLFTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/internal/LockFreeLinkedListAtomicStressLFTest.kt rename to kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAtomicStressLFTest.kt diff --git a/core/kotlinx-coroutines-core/test/internal/LockFreeLinkedListLongStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/internal/LockFreeLinkedListLongStressTest.kt rename to kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/internal/LockFreeLinkedListShortStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListShortStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/internal/LockFreeLinkedListShortStressTest.kt rename to kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListShortStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/internal/LockFreeLinkedListTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/internal/LockFreeLinkedListTest.kt rename to kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListTest.kt diff --git a/core/kotlinx-coroutines-core/test/internal/LockFreeTaskQueueStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeTaskQueueStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/internal/LockFreeTaskQueueStressTest.kt rename to kotlinx-coroutines-core/jvm/test/internal/LockFreeTaskQueueStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/internal/LockFreeTaskQueueTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeTaskQueueTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/internal/LockFreeTaskQueueTest.kt rename to kotlinx-coroutines-core/jvm/test/internal/LockFreeTaskQueueTest.kt diff --git a/core/kotlinx-coroutines-core/test/internal/ThreadSafeHeapTest.kt b/kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/internal/ThreadSafeHeapTest.kt rename to kotlinx-coroutines-core/jvm/test/internal/ThreadSafeHeapTest.kt diff --git a/core/kotlinx-coroutines-core/test/linearizability/ChannelIsClosedLinearizabilityTest.kt b/kotlinx-coroutines-core/jvm/test/linearizability/ChannelIsClosedLinearizabilityTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/linearizability/ChannelIsClosedLinearizabilityTest.kt rename to kotlinx-coroutines-core/jvm/test/linearizability/ChannelIsClosedLinearizabilityTest.kt diff --git a/core/kotlinx-coroutines-core/test/linearizability/ChannelLinearizabilityTest.kt b/kotlinx-coroutines-core/jvm/test/linearizability/ChannelLinearizabilityTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/linearizability/ChannelLinearizabilityTest.kt rename to kotlinx-coroutines-core/jvm/test/linearizability/ChannelLinearizabilityTest.kt diff --git a/core/kotlinx-coroutines-core/test/linearizability/LinTesting.kt b/kotlinx-coroutines-core/jvm/test/linearizability/LinTesting.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/linearizability/LinTesting.kt rename to kotlinx-coroutines-core/jvm/test/linearizability/LinTesting.kt diff --git a/core/kotlinx-coroutines-core/test/linearizability/LockFreeListLinearizabilityTest.kt b/kotlinx-coroutines-core/jvm/test/linearizability/LockFreeListLinearizabilityTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/linearizability/LockFreeListLinearizabilityTest.kt rename to kotlinx-coroutines-core/jvm/test/linearizability/LockFreeListLinearizabilityTest.kt diff --git a/core/kotlinx-coroutines-core/test/linearizability/LockFreeTaskQueueLinearizabilityTest.kt b/kotlinx-coroutines-core/jvm/test/linearizability/LockFreeTaskQueueLinearizabilityTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/linearizability/LockFreeTaskQueueLinearizabilityTest.kt rename to kotlinx-coroutines-core/jvm/test/linearizability/LockFreeTaskQueueLinearizabilityTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/BlockingCoroutineDispatcherRaceStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherRaceStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/BlockingCoroutineDispatcherRaceStressTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherRaceStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/BlockingCoroutineDispatcherStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/BlockingCoroutineDispatcherStressTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/BlockingCoroutineDispatcherTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/BlockingCoroutineDispatcherTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/BlockingIOTerminationStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingIOTerminationStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/BlockingIOTerminationStressTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/BlockingIOTerminationStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/CoroutineDispatcherTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineDispatcherTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/CoroutineDispatcherTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/CoroutineDispatcherTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/CoroutineSchedulerCloseStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerCloseStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/CoroutineSchedulerCloseStressTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerCloseStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/CoroutineSchedulerShrinkTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerShrinkTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/CoroutineSchedulerShrinkTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerShrinkTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/CoroutineSchedulerStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/CoroutineSchedulerStressTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/CoroutineSchedulerTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/CoroutineSchedulerTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/LimitingCoroutineDispatcherStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/LimitingCoroutineDispatcherStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/LimitingCoroutineDispatcherStressTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/LimitingCoroutineDispatcherStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/LimitingDispatcherTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/LimitingDispatcherTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/LimitingDispatcherTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/LimitingDispatcherTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/SchedulerTestBase.kt b/kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/SchedulerTestBase.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/SchedulerTestBase.kt diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/SharingWorkerClassTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/SharingWorkerClassTest.kt new file mode 100644 index 0000000000..6a66da9f5c --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/scheduling/SharingWorkerClassTest.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.scheduling + +import kotlinx.coroutines.* +import org.junit.Test +import kotlin.test.* + +class SharingWorkerClassTest : SchedulerTestBase() { + private val threadLocal = ThreadLocal() + + @Test + fun testSharedThread() = runTest { + val dispatcher = ExperimentalCoroutineDispatcher(1, schedulerName = "first") + val dispatcher2 = ExperimentalCoroutineDispatcher(1, schedulerName = "second") + + try { + withContext(dispatcher) { + assertNull(threadLocal.get()) + threadLocal.set(239) + withContext(dispatcher2) { + assertNull(threadLocal.get()) + threadLocal.set(42) + } + + assertEquals(239, threadLocal.get()) + } + } finally { + dispatcher.close() + dispatcher2.close() + } + } + + @Test(timeout = 5000L) + fun testProgress() = runTest { + // See #990 + val cores = Runtime.getRuntime().availableProcessors() + repeat(cores + 1) { + CoroutineScope(Dispatchers.Default).launch { + ExperimentalCoroutineDispatcher(1).close() + }.join() + } + } +} diff --git a/core/kotlinx-coroutines-core/test/scheduling/TestTimeSource.kt b/kotlinx-coroutines-core/jvm/test/scheduling/TestTimeSource.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/TestTimeSource.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/TestTimeSource.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/WorkQueueStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/WorkQueueStressTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/scheduling/WorkQueueTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/scheduling/WorkQueueTest.kt rename to kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt diff --git a/core/kotlinx-coroutines-core/test/selects/SelectChannelStressTest.kt b/kotlinx-coroutines-core/jvm/test/selects/SelectChannelStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/selects/SelectChannelStressTest.kt rename to kotlinx-coroutines-core/jvm/test/selects/SelectChannelStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/selects/SelectMutexStressTest.kt b/kotlinx-coroutines-core/jvm/test/selects/SelectMutexStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/selects/SelectMutexStressTest.kt rename to kotlinx-coroutines-core/jvm/test/selects/SelectMutexStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/selects/SelectPhilosophersStressTest.kt b/kotlinx-coroutines-core/jvm/test/selects/SelectPhilosophersStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/selects/SelectPhilosophersStressTest.kt rename to kotlinx-coroutines-core/jvm/test/selects/SelectPhilosophersStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/sync/MutexStressTest.kt b/kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/sync/MutexStressTest.kt rename to kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt diff --git a/core/kotlinx-coroutines-core/test/test/TestCoroutineContextTest.kt b/kotlinx-coroutines-core/jvm/test/test/TestCoroutineContextTest.kt similarity index 100% rename from core/kotlinx-coroutines-core/test/test/TestCoroutineContextTest.kt rename to kotlinx-coroutines-core/jvm/test/test/TestCoroutineContextTest.kt diff --git a/native/kotlinx-coroutines-core-native/README.md b/kotlinx-coroutines-core/native/README.md similarity index 100% rename from native/kotlinx-coroutines-core-native/README.md rename to kotlinx-coroutines-core/native/README.md diff --git a/native/kotlinx-coroutines-core-native/src/Builders.kt b/kotlinx-coroutines-core/native/src/Builders.kt similarity index 96% rename from native/kotlinx-coroutines-core-native/src/Builders.kt rename to kotlinx-coroutines-core/native/src/Builders.kt index c53831ac11..afeed7a508 100644 --- a/native/kotlinx-coroutines-core-native/src/Builders.kt +++ b/kotlinx-coroutines-core/native/src/Builders.kt @@ -52,6 +52,9 @@ private class BlockingCoroutine( parentContext: CoroutineContext, private val eventLoop: EventLoop? ) : AbstractCoroutine(parentContext, true) { + override val cancelsParent: Boolean + get() = false // it throws exception to parent instead of cancelling it + @Suppress("UNCHECKED_CAST") fun joinBlocking(): T { try { diff --git a/native/kotlinx-coroutines-core-native/src/CompletionHandler.kt b/kotlinx-coroutines-core/native/src/CompletionHandler.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/CompletionHandler.kt rename to kotlinx-coroutines-core/native/src/CompletionHandler.kt diff --git a/native/kotlinx-coroutines-core-native/src/CoroutineContext.kt b/kotlinx-coroutines-core/native/src/CoroutineContext.kt similarity index 96% rename from native/kotlinx-coroutines-core-native/src/CoroutineContext.kt rename to kotlinx-coroutines-core/native/src/CoroutineContext.kt index d846569126..6aef31090c 100644 --- a/native/kotlinx-coroutines-core-native/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/native/src/CoroutineContext.kt @@ -8,7 +8,7 @@ import kotlin.coroutines.* import kotlinx.coroutines.internal.* private fun takeEventLoop(): EventLoopImpl = - ThreadLocalEventLoop.currentOrNull() as EventLoopImpl ?: + ThreadLocalEventLoop.currentOrNull() as? EventLoopImpl ?: error("There is no event loop. Use runBlocking { ... } to start one.") internal object DefaultExecutor : CoroutineDispatcher(), Delay { diff --git a/native/kotlinx-coroutines-core-native/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt similarity index 90% rename from native/kotlinx-coroutines-core-native/src/CoroutineExceptionHandlerImpl.kt rename to kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt index 2219339af1..5efa9249ec 100644 --- a/native/kotlinx-coroutines-core-native/src/CoroutineExceptionHandlerImpl.kt +++ b/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt @@ -8,5 +8,5 @@ import kotlin.coroutines.* internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) { // log exception - println(exception) + exception.printStackTrace() } diff --git a/native/kotlinx-coroutines-core-native/src/Debug.kt b/kotlinx-coroutines-core/native/src/Debug.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/Debug.kt rename to kotlinx-coroutines-core/native/src/Debug.kt diff --git a/native/kotlinx-coroutines-core-native/src/Dispatchers.kt b/kotlinx-coroutines-core/native/src/Dispatchers.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/Dispatchers.kt rename to kotlinx-coroutines-core/native/src/Dispatchers.kt diff --git a/native/kotlinx-coroutines-core-native/src/EventLoop.kt b/kotlinx-coroutines-core/native/src/EventLoop.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/EventLoop.kt rename to kotlinx-coroutines-core/native/src/EventLoop.kt diff --git a/js/kotlinx-coroutines-core-js/src/Exceptions.kt b/kotlinx-coroutines-core/native/src/Exceptions.kt similarity index 74% rename from js/kotlinx-coroutines-core-js/src/Exceptions.kt rename to kotlinx-coroutines-core/native/src/Exceptions.kt index 431ff165aa..29c3ce5135 100644 --- a/js/kotlinx-coroutines-core-js/src/Exceptions.kt +++ b/kotlinx-coroutines-core/native/src/Exceptions.kt @@ -23,6 +23,13 @@ public actual class CompletionHandlerException public actual constructor( */ public actual open class CancellationException actual constructor(message: String?) : IllegalStateException(message) +/** + * Creates a cancellation exception with a specified message and [cause]. + */ +@Suppress("FunctionName") +public actual fun CancellationException(message: String?, cause: Throwable?) : CancellationException = + CancellationException(message.withCause(cause)) + /** * Thrown by cancellable suspending functions if the [Job] of the coroutine is cancelled or completed * without cause, or with a cause or exception that is not [CancellationException] @@ -41,14 +48,21 @@ internal actual class JobCancellationException public actual constructor( (message!!.hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ?: 0) } -internal actual class DispatchException actual constructor(message: String, cause: Throwable) : RuntimeException(message.withCause(cause)) +internal actual class CoroutinesInternalError actual constructor(message: String, cause: Throwable) : Error(message.withCause(cause)) @Suppress("FunctionName") internal fun IllegalStateException(message: String, cause: Throwable?) = IllegalStateException(message.withCause(cause)) -private fun String.withCause(cause: Throwable?) = - if (cause == null) this else "$this; caused by $cause" +private fun String?.withCause(cause: Throwable?) = + when { + cause == null -> this + this == null -> "caused by $cause" + else -> "$this; caused by $cause" + } @Suppress("NOTHING_TO_INLINE") -internal actual inline fun Throwable.addSuppressedThrowable(other: Throwable) { /* empty */ } \ No newline at end of file +internal actual inline fun Throwable.addSuppressedThrowable(other: Throwable) { /* empty */ } + +// For use in tests +internal actual val RECOVER_STACK_TRACES: Boolean = false diff --git a/native/kotlinx-coroutines-core-native/src/Runnable.kt b/kotlinx-coroutines-core/native/src/Runnable.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/Runnable.kt rename to kotlinx-coroutines-core/native/src/Runnable.kt diff --git a/native/kotlinx-coroutines-core-native/src/SchedulerTask.kt b/kotlinx-coroutines-core/native/src/SchedulerTask.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/SchedulerTask.kt rename to kotlinx-coroutines-core/native/src/SchedulerTask.kt diff --git a/native/kotlinx-coroutines-core-native/src/internal/ArrayCopy.kt b/kotlinx-coroutines-core/native/src/internal/ArrayCopy.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/internal/ArrayCopy.kt rename to kotlinx-coroutines-core/native/src/internal/ArrayCopy.kt diff --git a/native/kotlinx-coroutines-core-native/src/internal/Concurrent.kt b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/internal/Concurrent.kt rename to kotlinx-coroutines-core/native/src/internal/Concurrent.kt diff --git a/native/kotlinx-coroutines-core-native/src/internal/CopyOnWriteList.kt b/kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/internal/CopyOnWriteList.kt rename to kotlinx-coroutines-core/native/src/internal/CopyOnWriteList.kt diff --git a/native/kotlinx-coroutines-core-native/src/internal/LinkedList.kt b/kotlinx-coroutines-core/native/src/internal/LinkedList.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/internal/LinkedList.kt rename to kotlinx-coroutines-core/native/src/internal/LinkedList.kt diff --git a/native/kotlinx-coroutines-core-native/src/internal/LockFreeMPSCQueue.kt b/kotlinx-coroutines-core/native/src/internal/LockFreeMPSCQueue.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/internal/LockFreeMPSCQueue.kt rename to kotlinx-coroutines-core/native/src/internal/LockFreeMPSCQueue.kt diff --git a/native/kotlinx-coroutines-core-native/src/internal/ProbesSupport.kt b/kotlinx-coroutines-core/native/src/internal/ProbesSupport.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/internal/ProbesSupport.kt rename to kotlinx-coroutines-core/native/src/internal/ProbesSupport.kt diff --git a/native/kotlinx-coroutines-core-native/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/internal/StackTraceRecovery.kt rename to kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt diff --git a/native/kotlinx-coroutines-core-native/src/internal/Synchronized.kt b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/internal/Synchronized.kt rename to kotlinx-coroutines-core/native/src/internal/Synchronized.kt diff --git a/kotlinx-coroutines-core/native/src/internal/SystemProps.kt b/kotlinx-coroutines-core/native/src/internal/SystemProps.kt new file mode 100644 index 0000000000..6779d4e5d8 --- /dev/null +++ b/kotlinx-coroutines-core/native/src/internal/SystemProps.kt @@ -0,0 +1,7 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.internal + +internal actual fun systemProp(propertyName: String): String? = null \ No newline at end of file diff --git a/native/kotlinx-coroutines-core-native/src/internal/ThreadContext.kt b/kotlinx-coroutines-core/native/src/internal/ThreadContext.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/internal/ThreadContext.kt rename to kotlinx-coroutines-core/native/src/internal/ThreadContext.kt diff --git a/native/kotlinx-coroutines-core-native/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt similarity index 93% rename from native/kotlinx-coroutines-core-native/src/internal/ThreadLocal.kt rename to kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt index 7214bba872..c214a63bb6 100644 --- a/native/kotlinx-coroutines-core-native/src/internal/ThreadLocal.kt +++ b/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt @@ -3,7 +3,6 @@ */ package kotlinx.coroutines.internal -import kotlin.native.concurrent.* @Suppress("ACTUAL_WITHOUT_EXPECT") internal actual typealias NativeThreadLocal = kotlin.native.concurrent.ThreadLocal diff --git a/native/kotlinx-coroutines-core-native/src/internal/ThreadSafeHeap.kt b/kotlinx-coroutines-core/native/src/internal/ThreadSafeHeap.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/src/internal/ThreadSafeHeap.kt rename to kotlinx-coroutines-core/native/src/internal/ThreadSafeHeap.kt diff --git a/kotlinx-coroutines-core/native/test/DelayExceptionTest.kt b/kotlinx-coroutines-core/native/test/DelayExceptionTest.kt new file mode 100644 index 0000000000..463712ce47 --- /dev/null +++ b/kotlinx-coroutines-core/native/test/DelayExceptionTest.kt @@ -0,0 +1,28 @@ +/* + * 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.* +import kotlin.test.Test +import kotlin.test.assertTrue + +class DelayExceptionTest { + private object Dispatcher : CoroutineDispatcher() { + override fun isDispatchNeeded(context: CoroutineContext): Boolean = true + override fun dispatch(context: CoroutineContext, block: Runnable) { block.run() } + } + + private lateinit var exception: Throwable + + + @Test + fun testThrowsTce() { + CoroutineScope(Dispatcher + CoroutineExceptionHandler { _, e -> exception = e }).launch { + delay(10) + } + + assertTrue(exception is IllegalStateException) + } +} diff --git a/native/kotlinx-coroutines-core-native/test/TestBase.kt b/kotlinx-coroutines-core/native/test/TestBase.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/test/TestBase.kt rename to kotlinx-coroutines-core/native/test/TestBase.kt diff --git a/native/kotlinx-coroutines-core-native/test/internal/LinkedListTest.kt b/kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt similarity index 100% rename from native/kotlinx-coroutines-core-native/test/internal/LinkedListTest.kt rename to kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt diff --git a/core/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md similarity index 64% rename from core/kotlinx-coroutines-debug/README.md rename to kotlinx-coroutines-debug/README.md index b9cc094a66..b8475f19f4 100644 --- a/core/kotlinx-coroutines-debug/README.md +++ b/kotlinx-coroutines-debug/README.md @@ -10,7 +10,7 @@ Call to [DebugProbes.install] installs debug agent via ByteBuddy and starts spyi After that, you can use [DebugProbes.dumpCoroutines] to print all active (suspended or running) coroutines, including their state, creation and suspension stacktraces. -Additionally, it is possible to process the list of such coroutines via [DebugProbes.dumpCoroutinesState] or dump isolated parts +Additionally, it is possible to process the list of such coroutines via [DebugProbes.dumpCoroutinesInfo] or dump isolated parts of coroutines hierarchy referenced by a [Job] or [CoroutineScope] instances using [DebugProbes.printJob] and [DebugProbes.printScope] respectively. ### Using in your project @@ -18,14 +18,46 @@ of coroutines hierarchy referenced by a [Job] or [CoroutineScope] instances usin Add `kotlinx-coroutines-debug` to your project test dependencies: ``` dependencies { - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.1.1' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.2.0-alpha' } ``` +### Using in unit tests + +For JUnit4 debug module provides special test rule, [CoroutinesTimeout], for installing debug probes +and to dump coroutines on timeout to simplify tests debugging. + +Its usage is better demonstrated by the example (runnable code is [here](test/TestRuleExample.kt)): + +```kotlin +class TestRuleExample { + @Rule + @JvmField + public val timeout = CoroutinesTimeout.seconds(1) + + private suspend fun someFunctionDeepInTheStack() { + withContext(Dispatchers.IO) { + delay(Long.MAX_VALUE) // Hang method + } + } + + @Test + fun hangingTest() = runBlocking { + val job = launch { + someFunctionDeepInTheStack() + } + job.join() // Join will hang + } +} +``` + +After 1 second, test will fail with `TestTimeoutException` and all coroutines (`runBlocking` and `launch`) and their +stacktraces will be dumped to the console. + ### 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.1.1.jar`. +You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.2.0-alpha.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. @@ -100,6 +132,24 @@ Dumping only deferred API is purely experimental and it is not guaranteed that it won't be changed (while it is marked as `@ExperimentalCoroutinesApi`). Do not use this module in production environment and do not rely on the format of the data produced by [DebugProbes]. +### Debug agent and Android + +Unfortunately, Android runtime does not support Instrument API necessary for `kotlinx-coroutines-debug` to function, triggering `java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;`. + +Nevertheless, it will be possible to support debug agent on Android as soon as [GradleAspectJ-Android](https://github.com/Archinamon/android-gradle-aspectj) will support androin-gradle 3.3 + + + [Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html @@ -109,7 +159,9 @@ Do not use this module in production environment and do not rely on the format o [DebugProbes]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/index.html [DebugProbes.install]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/install.html [DebugProbes.dumpCoroutines]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines.html -[DebugProbes.dumpCoroutinesState]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines-state.html -[DebugProbes.printScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-scope.html +[DebugProbes.dumpCoroutinesInfo]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/dump-coroutines-info.html [DebugProbes.printJob]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-job.html +[DebugProbes.printScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug/-debug-probes/print-scope.html + +[CoroutinesTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-debug/kotlinx.coroutines.debug.junit4/-coroutines-timeout/index.html diff --git a/core/kotlinx-coroutines-debug/build.gradle b/kotlinx-coroutines-debug/build.gradle similarity index 94% rename from core/kotlinx-coroutines-debug/build.gradle rename to kotlinx-coroutines-debug/build.gradle index d54b2b67df..e96cc6c634 100644 --- a/core/kotlinx-coroutines-debug/build.gradle +++ b/kotlinx-coroutines-debug/build.gradle @@ -5,6 +5,7 @@ apply plugin: "com.github.johnrengelman.shadow" dependencies { + compileOnly "junit:junit:$junit_version" compile "net.bytebuddy:byte-buddy:$byte_buddy_version" compile "net.bytebuddy:byte-buddy-agent:$byte_buddy_version" } diff --git a/core/kotlinx-coroutines-debug/src/AgentPremain.kt b/kotlinx-coroutines-debug/src/AgentPremain.kt similarity index 100% rename from core/kotlinx-coroutines-debug/src/AgentPremain.kt rename to kotlinx-coroutines-debug/src/AgentPremain.kt diff --git a/core/kotlinx-coroutines-debug/src/CoroutineState.kt b/kotlinx-coroutines-debug/src/CoroutineInfo.kt similarity index 79% rename from core/kotlinx-coroutines-debug/src/CoroutineState.kt rename to kotlinx-coroutines-debug/src/CoroutineInfo.kt index 3441de8319..2d4b6805f1 100644 --- a/core/kotlinx-coroutines-debug/src/CoroutineState.kt +++ b/kotlinx-coroutines-debug/src/CoroutineInfo.kt @@ -12,11 +12,11 @@ import kotlin.coroutines.* import kotlin.coroutines.jvm.internal.* /** - * Class describing coroutine state. + * Class describing coroutine info such as its context, state and stacktrace. */ @ExperimentalCoroutinesApi -public data class CoroutineState internal constructor( - public val continuation: Continuation<*>, +public data class CoroutineInfo internal constructor( + val context: CoroutineContext, private val creationStackBottom: CoroutineStackFrame, @JvmField internal val sequenceNumber: Long ) { @@ -25,7 +25,7 @@ public data class CoroutineState internal constructor( * [Job] associated with a current coroutine or null. * May be later used in [DebugProbes.printJob]. */ - public val jobOrNull: Job? get() = continuation.context[Job] + public val job: Job? get() = context[Job] /** * Creation stacktrace of the coroutine. @@ -39,11 +39,15 @@ public data class CoroutineState internal constructor( private var _state: State = State.CREATED - private var lastObservedFrame: CoroutineStackFrame? = null + @JvmField + internal var lastObservedThread: Thread? = null + + @JvmField + internal var lastObservedFrame: CoroutineStackFrame? = null // Copy constructor - internal constructor(coroutine: Continuation<*>, state: CoroutineState) : this( - coroutine, + internal constructor(coroutine: Continuation<*>, state: CoroutineInfo) : this( + coroutine.context, state.creationStackBottom, state.sequenceNumber ) { @@ -51,6 +55,21 @@ public data class CoroutineState internal constructor( this.lastObservedFrame = state.lastObservedFrame } + /** + * Last observed stacktrace of the coroutine captured on its suspension or resumption point. + * It means that for [running][State.RUNNING] coroutines resulting stacktrace is inaccurate and + * reflects stacktrace of the resumption point, not the actual current stacktrace. + */ + public fun lastObservedStackTrace(): List { + var frame: CoroutineStackFrame? = lastObservedFrame ?: return emptyList() + val result = ArrayList() + while (frame != null) { + frame.getStackTraceElement()?.let { result.add(sanitize(it)) } + frame = frame.callerFrame + } + return result + } + private fun creationStackTrace(): List { // Skip "Coroutine creation stacktrace" frame return sequence { yieldFrames(creationStackBottom.callerFrame) }.toList() @@ -66,25 +85,15 @@ public data class CoroutineState internal constructor( } internal fun updateState(state: State, frame: Continuation<*>) { - if (_state == state && lastObservedFrame != null) return + // Propagate only duplicating transitions to running for KT-29997 + if (_state == state && state == State.SUSPENDED && lastObservedFrame != null) return _state = state lastObservedFrame = frame as? CoroutineStackFrame - } - - /** - * Last observed stacktrace of the coroutine captured on its suspension or resumption point. - * It means that for [running][State.RUNNING] coroutines resulting stacktrace is inaccurate and - * reflects stacktrace of the resumption point, not the actual current stacktrace. - */ - public fun lastObservedStackTrace(): List { - var frame: CoroutineStackFrame? = lastObservedFrame ?: return emptyList() - val result = ArrayList() - while (frame != null) { - frame.getStackTraceElement()?.let { result.add(sanitize(it)) } - frame = frame.callerFrame + if (state == State.RUNNING) { + lastObservedThread = Thread.currentThread() + } else { + lastObservedThread = null } - - return result } } diff --git a/core/kotlinx-coroutines-debug/src/DebugProbes.kt b/kotlinx-coroutines-debug/src/DebugProbes.kt similarity index 97% rename from core/kotlinx-coroutines-debug/src/DebugProbes.kt rename to kotlinx-coroutines-debug/src/DebugProbes.kt index af30c5e811..a81fd7a880 100644 --- a/core/kotlinx-coroutines-debug/src/DebugProbes.kt +++ b/kotlinx-coroutines-debug/src/DebugProbes.kt @@ -95,10 +95,10 @@ public object DebugProbes { printJob(scope.coroutineContext[Job] ?: error("Job is not present in the scope"), out) /** - * Returns all existing coroutine states. + * Returns all existing coroutines info. * The resulting collection represents a consistent snapshot of all existing coroutines at the moment of invocation. */ - public fun dumpCoroutinesState(): List = DebugProbesImpl.dumpCoroutinesState() + public fun dumpCoroutinesInfo(): List = DebugProbesImpl.dumpCoroutinesInfo() /** * Dumps all active coroutines into the given output stream, providing a consistent snapshot of all existing coroutines at the moment of invocation. diff --git a/kotlinx-coroutines-debug/src/internal/DebugProbesImpl.kt b/kotlinx-coroutines-debug/src/internal/DebugProbesImpl.kt new file mode 100644 index 0000000000..29b1e36e09 --- /dev/null +++ b/kotlinx-coroutines-debug/src/internal/DebugProbesImpl.kt @@ -0,0 +1,417 @@ +/* + * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.debug.internal + +import kotlinx.coroutines.* +import kotlinx.coroutines.debug.* +import net.bytebuddy.* +import net.bytebuddy.agent.* +import net.bytebuddy.dynamic.loading.* +import java.io.* +import java.text.* +import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.coroutines.* +import kotlin.coroutines.jvm.internal.* +import kotlinx.coroutines.internal.artificialFrame as createArtificialFrame // IDEA bug workaround + +/** + * Mirror of [DebugProbes] with actual implementation. + * [DebugProbes] are implemented with pimpl to simplify user-facing class and make it look simple and + * documented. + */ +internal object DebugProbesImpl { + private const val ARTIFICIAL_FRAME_MESSAGE = "Coroutine creation stacktrace" + private val dateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss") + private val capturedCoroutines = HashSet>() + @Volatile + private var installations = 0 + private val isInstalled: Boolean get() = installations > 0 + // To sort coroutines by creation order, used as unique id + private var sequenceNumber: Long = 0 + + /* + * This is an optimization in the face of KT-29997: + * Consider suspending call stack a()->b()->c() and c() completes its execution and every call is + * "almost" in tail position. + * + * Then at least three RUNNING -> RUNNING transitions will occur consecutively and complexity of each is O(depth). + * To avoid that quadratic complexity, we are caching lookup result for such chains in this map and update it incrementally. + */ + private val callerInfoCache = HashMap() + + @Synchronized + public fun install() { + if (++installations > 1) return + + ByteBuddyAgent.install() + val cl = Class.forName("kotlin.coroutines.jvm.internal.DebugProbesKt") + val cl2 = Class.forName("kotlinx.coroutines.debug.DebugProbesKt") + + ByteBuddy() + .redefine(cl2) + .name(cl.name) + .make() + .load(cl.classLoader, ClassReloadingStrategy.fromInstalledAgent()) + } + + @Synchronized + public fun uninstall() { + check(isInstalled) { "Agent was not installed" } + if (--installations != 0) return + + capturedCoroutines.clear() + callerInfoCache.clear() + val cl = Class.forName("kotlin.coroutines.jvm.internal.DebugProbesKt") + val cl2 = Class.forName("kotlinx.coroutines.debug.internal.NoOpProbesKt") + + ByteBuddy() + .redefine(cl2) + .name(cl.name) + .make() + .load(cl.classLoader, ClassReloadingStrategy.fromInstalledAgent()) + } + + @Synchronized + public fun hierarchyToString(job: Job): String { + check(isInstalled) { "Debug probes are not installed" } + val jobToStack = capturedCoroutines + .filter { it.delegate.context[Job] != null } + .associateBy({ it.delegate.context[Job]!! }, {it.info}) + return buildString { + job.build(jobToStack, this, "") + } + } + + private fun Job.build(map: Map, builder: StringBuilder, indent: String) { + val info = map[this] + val newIndent: String + if (info == null) { // Append coroutine without stacktrace + // Do not print scoped coroutines and do not increase indentation level + @Suppress("INVISIBLE_REFERENCE") + if (this !is kotlinx.coroutines.internal.ScopeCoroutine<*>) { + builder.append("$indent$debugString\n") + newIndent = indent + "\t" + } else { + newIndent = indent + } + } else { + // Append coroutine with its last stacktrace element + val element = info.lastObservedStackTrace().firstOrNull() + val state = info.state + builder.append("$indent$debugString, continuation is $state at line $element\n") + newIndent = indent + "\t" + } + // Append children with new indent + for (child in children) { + child.build(map, builder, newIndent) + } + } + + @Suppress("DEPRECATION_ERROR") // JobSupport + private val Job.debugString: String get() = if (this is JobSupport) toDebugString() else toString() + + @Synchronized + public fun dumpCoroutinesInfo(): List { + check(isInstalled) { "Debug probes are not installed" } + return capturedCoroutines.asSequence() + .map { CoroutineInfo(it.delegate, it.info) } + .sortedBy { it.sequenceNumber } + .toList() + } + + public fun dumpCoroutines(out: PrintStream) { + // Avoid inference with other out/err invocations by creating a string first + dumpCoroutines().let { out.println(it) } + } + + @Synchronized + private fun dumpCoroutines(): String = buildString { + check(isInstalled) { "Debug probes are not installed" } + // Synchronization window can be reduce even more, but no need to do it here + append("Coroutines dump ${dateFormat.format(System.currentTimeMillis())}") + capturedCoroutines + .asSequence() + .sortedBy { it.info.sequenceNumber } + .forEach { owner -> + val info = owner.info + val observedStackTrace = info.lastObservedStackTrace() + val enhancedStackTrace = enhanceStackTraceWithThreadDump(info, observedStackTrace) + val state = if (info.state == State.RUNNING && enhancedStackTrace === observedStackTrace) + "${info.state} (Last suspension stacktrace, not an actual stacktrace)" + else + info.state.toString() + + append("\n\nCoroutine ${owner.delegate}, state: $state") + if (observedStackTrace.isEmpty()) { + append("\n\tat ${createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE)}") + printStackTrace(info.creationStackTrace) + } else { + printStackTrace(enhancedStackTrace) + } + } + } + + /** + * Tries to enhance [coroutineTrace] (obtained by call to [CoroutineInfo.lastObservedStackTrace]) with + * thread dump of [CoroutineInfo.lastObservedThread]. + * + * Returns [coroutineTrace] if enhancement was unsuccessful or the enhancement result. + */ + private fun enhanceStackTraceWithThreadDump( + info: CoroutineInfo, + coroutineTrace: List + ): List { + val thread = info.lastObservedThread + if (info.state != State.RUNNING || thread == null) return coroutineTrace + // Avoid security manager issues + val actualTrace = runCatching { thread.stackTrace }.getOrNull() + ?: return coroutineTrace + + /* + * Here goes heuristic that tries to merge two stacktraces: real one + * (that has at least one but usually not so many suspend function frames) + * and coroutine one that has only suspend function frames. + * + * Heuristic: + * 1) Dump lastObservedThread + * 2) Find the next frame after BaseContinuationImpl.resumeWith (continuation machinery). + * Invariant: this method is called under the lock, so such method **should** be present + * in continuation stacktrace. + * 3) Find target method in continuation stacktrace (metadata-based) + * 4) Prepend dumped stacktrace (trimmed by target frame) to continuation stacktrace + * + * Heuristic may fail on recursion and overloads, but it will be automatically improved + * with KT-29997. + */ + val indexOfResumeWith = actualTrace.indexOfFirst { + it.className == "kotlin.coroutines.jvm.internal.BaseContinuationImpl" && + it.methodName == "resumeWith" && + it.fileName == "ContinuationImpl.kt" + } + + val (continuationStartFrame, frameSkipped) = findContinuationStartIndex( + indexOfResumeWith, + actualTrace, + coroutineTrace) + + if (continuationStartFrame == -1) return coroutineTrace + + val delta = if (frameSkipped) 1 else 0 + val expectedSize = indexOfResumeWith + coroutineTrace.size - continuationStartFrame - 1 - delta + val result = ArrayList(expectedSize) + for (index in 0 until indexOfResumeWith - delta) { + result += actualTrace[index] + } + + for (index in continuationStartFrame + 1 until coroutineTrace.size) { + result += coroutineTrace[index] + } + + return result + } + + /** + * Tries to find the lowest meaningful frame above `resumeWith` in the real stacktrace and + * its match in a coroutines stacktrace (steps 2-3 in heuristic). + * + * This method does more than just matching `realTrace.indexOf(resumeWith) - 1`: + * If method above `resumeWith` has no line number (thus it is `stateMachine.invokeSuspend`), + * it's skipped and attempt to match next one is made because state machine could have been missing in the original coroutine stacktrace. + * + * Returns index of such frame (or -1) and flag indicating whether frame with state machine was skipped + */ + private fun findContinuationStartIndex( + indexOfResumeWith: Int, + actualTrace: Array, + coroutineTrace: List + ): Pair { + val result = findIndexOfFrame(indexOfResumeWith - 1, actualTrace, coroutineTrace) + if (result == -1) return findIndexOfFrame(indexOfResumeWith - 2, actualTrace, coroutineTrace) to true + return result to false + } + + private fun findIndexOfFrame( + frameIndex: Int, + actualTrace: Array, + coroutineTrace: List + ): Int { + val continuationFrame = actualTrace.getOrNull(frameIndex) + ?: return -1 + + return coroutineTrace.indexOfFirst { + it.fileName == continuationFrame.fileName && + it.className == continuationFrame.className && + it.methodName == continuationFrame.methodName + } + } + + private fun StringBuilder.printStackTrace(frames: List) { + frames.forEach { frame -> + append("\n\tat $frame") + } + } + + internal fun probeCoroutineResumed(frame: Continuation<*>) = updateState(frame, State.RUNNING) + + internal fun probeCoroutineSuspended(frame: Continuation<*>) = updateState(frame, State.SUSPENDED) + + private fun updateState(frame: Continuation<*>, state: State) { + // KT-29997 is here only since 1.3.30 + if (state == State.RUNNING && KotlinVersion.CURRENT.isAtLeast(1, 3, 30)) { + val stackFrame = frame as? CoroutineStackFrame ?: return + updateRunningState(stackFrame, state) + return + } + + // Find ArtificialStackFrame of the coroutine + val owner = frame.owner() ?: return + updateState(owner, frame, state) + } + + @Synchronized // See comment to callerInfoCache + private fun updateRunningState(frame: CoroutineStackFrame, state: State) { + if (!isInstalled) return + // Lookup coroutine info in cache or by traversing stack frame + val info: CoroutineInfo + val cached = callerInfoCache.remove(frame) + if (cached != null) { + info = cached + } else { + info = frame.owner()?.info ?: return + // Guard against improper implementations of CoroutineStackFrame and bugs in the compiler + callerInfoCache.remove(info.lastObservedFrame?.realCaller()) + } + + info.updateState(state, frame as Continuation<*>) + // Do not cache it for proxy-classes such as ScopeCoroutines + val caller = frame.realCaller() ?: return + callerInfoCache[caller] = info + } + + private tailrec fun CoroutineStackFrame.realCaller(): CoroutineStackFrame? { + val caller = callerFrame ?: return null + return if (caller.getStackTraceElement() != null) caller else caller.realCaller() + } + + @Synchronized + private fun updateState(owner: CoroutineOwner<*>, frame: Continuation<*>, state: State) { + if (!isInstalled) return + owner.info.updateState(state, frame) + } + + private fun Continuation<*>.owner(): CoroutineOwner<*>? = (this as? CoroutineStackFrame)?.owner() + + private tailrec fun CoroutineStackFrame.owner(): CoroutineOwner<*>? = + if (this is CoroutineOwner<*>) this else callerFrame?.owner() + + internal fun probeCoroutineCreated(completion: Continuation): Continuation { + if (!isInstalled) return completion + /* + * If completion already has an owner, it means that we are in scoped coroutine (coroutineScope, withContext etc.), + * then piggyback on its already existing owner and do not replace completion + */ + val owner = completion.owner() + if (owner != null) return completion + /* + * Here we replace completion with a sequence of CoroutineStackFrame objects + * which represents creation stacktrace, thus making stacktrace recovery mechanism + * even more verbose (it will attach coroutine creation stacktrace to all exceptions), + * and then using CoroutineOwner completion as unique identifier of coroutineSuspended/resumed calls. + */ + val stacktrace = sanitizeStackTrace(Exception()) + val frame = stacktrace.foldRight(null) { frame, acc -> + object : CoroutineStackFrame { + override val callerFrame: CoroutineStackFrame? = acc + override fun getStackTraceElement(): StackTraceElement = frame + } + }!! + + return createOwner(completion, frame) + } + + @Synchronized + private fun createOwner(completion: Continuation, frame: CoroutineStackFrame): Continuation { + if (!isInstalled) return completion + val info = CoroutineInfo(completion.context, frame, ++sequenceNumber) + val owner = CoroutineOwner(completion, info, frame) + capturedCoroutines += owner + return owner + } + + @Synchronized + private fun probeCoroutineCompleted(owner: CoroutineOwner<*>) { + capturedCoroutines.remove(owner) + /* + * This removal is a guard against improperly implemented CoroutineStackFrame + * and bugs in the compiler. + */ + val caller = owner.info.lastObservedFrame?.realCaller() + callerInfoCache.remove(caller) + } + + /** + * This class is injected as completion of all continuations in [probeCoroutineCompleted]. + * It is owning the coroutine info and responsible for managing all its external info related to debug agent. + */ + private class CoroutineOwner( + @JvmField val delegate: Continuation, + @JvmField val info: CoroutineInfo, + frame: CoroutineStackFrame + ) : Continuation by delegate, CoroutineStackFrame by frame { + override fun resumeWith(result: Result) { + probeCoroutineCompleted(this) + delegate.resumeWith(result) + } + + override fun toString(): String = delegate.toString() + } + + private fun sanitizeStackTrace(throwable: T): List { + val stackTrace = throwable.stackTrace + val size = stackTrace.size + val probeIndex = stackTrace.indexOfLast { it.className == "kotlin.coroutines.jvm.internal.DebugProbesKt" } + + if (!DebugProbes.sanitizeStackTraces) { + return List(size - probeIndex) { + if (it == 0) createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE) else stackTrace[it + probeIndex] + } + } + + /* + * Trim intervals of internal methods from the stacktrace (bounds are excluded from trimming) + * E.g. for sequence [e, i1, i2, i3, e, i4, e, i5, i6, e7] + * output will be [e, i1, i3, e, i4, e, i5, i7] + */ + val result = ArrayList(size - probeIndex + 1) + result += createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE) + var includeInternalFrame = true + for (i in (probeIndex + 1) until size - 1) { + val element = stackTrace[i] + if (!element.isInternalMethod) { + includeInternalFrame = true + result += element + continue + } + + if (includeInternalFrame) { + result += element + includeInternalFrame = false + } else if (stackTrace[i + 1].isInternalMethod) { + continue + } else { + result += element + includeInternalFrame = true + } + + } + + result += stackTrace[size - 1] + return result + } + + private val StackTraceElement.isInternalMethod: Boolean get() = className.startsWith("kotlinx.coroutines") +} diff --git a/core/kotlinx-coroutines-debug/src/internal/NoOpProbes.kt b/kotlinx-coroutines-debug/src/internal/NoOpProbes.kt similarity index 100% rename from core/kotlinx-coroutines-debug/src/internal/NoOpProbes.kt rename to kotlinx-coroutines-debug/src/internal/NoOpProbes.kt diff --git a/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeout.kt b/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeout.kt new file mode 100644 index 0000000000..c69becb24c --- /dev/null +++ b/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeout.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.debug.junit4 + +import kotlinx.coroutines.debug.* +import org.junit.rules.* +import org.junit.runner.* +import org.junit.runners.model.* +import java.util.concurrent.* + +/** + * Coroutines timeout rule for JUnit4 that is applied to all methods in the class. + * This rule is very similar to [Timeout] rule: it runs tests in a separate thread, + * fails tests after the given timeout and interrupts test thread. + * + * Additionally, this rule installs [DebugProbes] and dumps all coroutines at the moment of the timeout. + * It may cancel coroutines on timeout if [cancelOnTimeout] set to `true`. + * + * Example of usage: + * ``` + * class HangingTest { + * + * @Rule + * @JvmField + * val timeout = CoroutinesTimeout.seconds(5) + * + * @Test + * fun testThatHangs() = runBlocking { + * ... + * delay(Long.MAX_VALUE) // somewhere deep in the stack + * ... + * } + * } + * ``` + */ +public class CoroutinesTimeout( + private val testTimeoutMs: Long, + private val cancelOnTimeout: Boolean = false +) : TestRule { + + init { + require(testTimeoutMs > 0) { "Expected positive test timeout, but had $testTimeoutMs" } + } + + companion object { + /** + * Creates [CoroutinesTimeout] rule with the given timeout in seconds. + */ + public fun seconds(seconds: Int, cancelOnTimeout: Boolean = false): CoroutinesTimeout = + CoroutinesTimeout(TimeUnit.SECONDS.toMillis(seconds.toLong()), cancelOnTimeout) + } + + /** + * @suppress suppress from Dokka + */ + override fun apply(base: Statement, description: Description): Statement = + CoroutinesTimeoutStatement(base, description, testTimeoutMs, cancelOnTimeout) +} diff --git a/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeoutStatement.kt b/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeoutStatement.kt new file mode 100644 index 0000000000..a00a17d58d --- /dev/null +++ b/kotlinx-coroutines-debug/src/junit4/CoroutinesTimeoutStatement.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.debug.junit4 + +import kotlinx.coroutines.debug.* +import org.junit.runner.* +import org.junit.runners.model.* +import java.util.concurrent.* + +internal class CoroutinesTimeoutStatement( + testStatement: Statement, + private val testDescription: Description, + private val testTimeoutMs: Long, + private val cancelOnTimeout: Boolean = false +) : Statement() { + + private val testStartedLatch = CountDownLatch(1) + + private val testResult = FutureTask { + testStartedLatch.countDown() + testStatement.evaluate() + } + + /* + * We are using hand-rolled thread instead of single thread executor + * in order to be able to safely interrupt thread in the end of a test + */ + private val testThread = Thread(testResult, "Timeout test thread").apply { isDaemon = true } + + override fun evaluate() { + DebugProbes.install() + testThread.start() + // Await until test is started to take only test execution time into account + testStartedLatch.await() + try { + testResult.get(testTimeoutMs, TimeUnit.MILLISECONDS) + return + } catch (e: TimeoutException) { + handleTimeout(testDescription) + } catch (e: ExecutionException) { + throw e.cause ?: e + } finally { + DebugProbes.uninstall() + } + } + + private fun handleTimeout(description: Description) { + val units = + if (testTimeoutMs % 1000 == 0L) + "${testTimeoutMs / 1000} seconds" + else "$testTimeoutMs milliseconds" + + val message = "Test ${description.methodName} timed out after $units" + System.err.println("\n$message\n") + System.err.flush() + + DebugProbes.dumpCoroutines() + System.out.flush() // Synchronize serr/sout + + /* + * Order is important: + * 1) Create exception with a stacktrace of hang test + * 2) Cancel all coroutines via debug agent API (changing system state!) + * 3) Throw created exception + */ + val exception = createTimeoutException(message, testThread) + cancelIfNecessary() + // If timed out test throws an exception, we can't do much except ignoring it + throw exception + } + + private fun cancelIfNecessary() { + if (cancelOnTimeout) { + DebugProbes.dumpCoroutinesInfo().forEach { + it.job?.cancel() + } + } + } + + private fun createTimeoutException(message: String, thread: Thread): Exception { + val stackTrace = thread.stackTrace + val exception = TestTimedOutException(testTimeoutMs, TimeUnit.MILLISECONDS) + exception.stackTrace = stackTrace + thread.interrupt() + return exception + } +} diff --git a/core/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt b/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt similarity index 90% rename from core/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt rename to kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt index 8a4f4e6962..35e9d1e9f2 100644 --- a/core/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt +++ b/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt @@ -5,32 +5,13 @@ package kotlinx.coroutines.debug import kotlinx.coroutines.* -import org.junit.* import org.junit.Test import kotlin.coroutines.* import kotlin.test.* -@Suppress("SUSPENSION_POINT_INSIDE_MONITOR") // bug in 1.3.0 FE -class CoroutinesDumpTest : TestBase() { - +class CoroutinesDumpTest : DebugTestBase() { private val monitor = Any() - @Before - fun setUp() { - before() - DebugProbes.sanitizeStackTraces = false - DebugProbes.install() - } - - @After - fun tearDown() { - try { - DebugProbes.uninstall() - } finally { - onCompletion() - } - } - @Test fun testSuspendedCoroutine() = synchronized(monitor) { val deferred = GlobalScope.async { @@ -49,8 +30,8 @@ class CoroutinesDumpTest : TestBase() { "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" + "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n") - val found = DebugProbes.dumpCoroutinesState().single { it.jobOrNull === deferred } - assertSame(deferred, found.jobOrNull) + val found = DebugProbes.dumpCoroutinesInfo().single { it.job === deferred } + assertSame(deferred, found.job) runBlocking { deferred.cancelAndJoin() } } @@ -81,11 +62,14 @@ class CoroutinesDumpTest : TestBase() { fun testRunningCoroutineWithSuspensionPoint() = synchronized(monitor) { val deferred = GlobalScope.async { activeMethod(shouldSuspend = true) + yield() // tail-call } awaitCoroutineStarted() + Thread.sleep(10) verifyDump( - "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@1e4a7dd4, state: RUNNING (Last suspension stacktrace, not an actual stacktrace)\n" + + "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@1e4a7dd4, state: RUNNING\n" + + "\tat java.lang.Thread.sleep(Native Method)\n" + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.nestedActiveMethod(CoroutinesDumpTest.kt:111)\n" + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.activeMethod(CoroutinesDumpTest.kt:106)\n" + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest\$testRunningCoroutineWithSuspensionPoint\$1\$deferred\$1.invokeSuspend(CoroutinesDumpTest.kt:71)\n" + @@ -110,7 +94,7 @@ class CoroutinesDumpTest : TestBase() { } awaitCoroutineStarted() - val coroutine = DebugProbes.dumpCoroutinesState().first() + val coroutine = DebugProbes.dumpCoroutinesInfo().first() val result = coroutine.creationStackTrace.fold(StringBuilder()) { acc, element -> acc.append(element.toString()) acc.append('\n') @@ -143,11 +127,11 @@ class CoroutinesDumpTest : TestBase() { private suspend fun activeMethod(shouldSuspend: Boolean) { nestedActiveMethod(shouldSuspend) - delay(1) + assertTrue(true) // tail-call } private suspend fun nestedActiveMethod(shouldSuspend: Boolean) { - if (shouldSuspend) delay(1) + if (shouldSuspend) yield() notifyTest() while (coroutineContext[Job]!!.isActive) { Thread.sleep(100) @@ -156,11 +140,11 @@ class CoroutinesDumpTest : TestBase() { private suspend fun sleepingOuterMethod() { sleepingNestedMethod() - delay(1) + yield() } private suspend fun sleepingNestedMethod() { - delay(1) + yield() notifyTest() delay(Long.MAX_VALUE) } diff --git a/core/kotlinx-coroutines-debug/test/DebugProbesTest.kt b/kotlinx-coroutines-debug/test/DebugProbesTest.kt similarity index 100% rename from core/kotlinx-coroutines-debug/test/DebugProbesTest.kt rename to kotlinx-coroutines-debug/test/DebugProbesTest.kt diff --git a/kotlinx-coroutines-debug/test/DebugTestBase.kt b/kotlinx-coroutines-debug/test/DebugTestBase.kt new file mode 100644 index 0000000000..7ce214992b --- /dev/null +++ b/kotlinx-coroutines-debug/test/DebugTestBase.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package kotlinx.coroutines.debug + + +import kotlinx.coroutines.* +import kotlinx.coroutines.debug.junit4.* +import org.junit.* + +open class DebugTestBase : TestBase() { + + @JvmField + @Rule + val timeout = CoroutinesTimeout.seconds(10) + + @Before + open fun setUp() { + before() + DebugProbes.sanitizeStackTraces = false + DebugProbes.install() + } + + @After + fun tearDown() { + try { + DebugProbes.uninstall() + } finally { + onCompletion() + } + } +} \ No newline at end of file diff --git a/core/kotlinx-coroutines-debug/test/Example.kt b/kotlinx-coroutines-debug/test/Example.kt similarity index 100% rename from core/kotlinx-coroutines-debug/test/Example.kt rename to kotlinx-coroutines-debug/test/Example.kt diff --git a/kotlinx-coroutines-debug/test/RecoveryExample.kt b/kotlinx-coroutines-debug/test/RecoveryExample.kt new file mode 100644 index 0000000000..2280dd1694 --- /dev/null +++ b/kotlinx-coroutines-debug/test/RecoveryExample.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +@file:Suppress("PackageDirectoryMismatch") +package example + +import kotlinx.coroutines.* + +object PublicApiImplementation : CoroutineScope by CoroutineScope(CoroutineName("Example")) { + + private fun doWork(): Int { + error("Internal invariant failed") + } + + private fun asynchronousWork(): Int { + return doWork() + 1 + } + + public suspend fun awaitAsynchronousWorkInMainThread() { + val task = async(Dispatchers.Default) { + asynchronousWork() + } + + task.await() + } +} + +suspend fun main() { + // Try to switch debug mode on and off to see the difference + PublicApiImplementation.awaitAsynchronousWorkInMainThread() +} diff --git a/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt new file mode 100644 index 0000000000..72082942e8 --- /dev/null +++ b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt @@ -0,0 +1,169 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package kotlinx.coroutines.debug + +import kotlinx.coroutines.* +import org.junit.Test +import java.util.concurrent.* +import kotlin.test.* + +class RunningThreadStackMergeTest : DebugTestBase() { + + private val testMainBlocker = CountDownLatch(1) // Test body blocks on it + private val coroutineBlocker = CyclicBarrier(2) // Launched coroutine blocks on it + + @Test + fun testStackMergeWithContext() = runTest { + launchCoroutine() + awaitCoroutineStarted() + verifyDump( + "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored + "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@50284dc4, state: RUNNING\n" + + "\tat sun.misc.Unsafe.park(Native Method)\n" + + "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" + + "\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" + + "\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" + + "\tat java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.nonSuspendingFun(RunningThreadStackMergeTest.kt:86)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.access\$nonSuspendingFun(RunningThreadStackMergeTest.kt:12)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$suspendingFunction\$2.invokeSuspend(RunningThreadStackMergeTest.kt:77)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.suspendingFunction(RunningThreadStackMergeTest.kt:75)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchCoroutine\$1.invokeSuspend(RunningThreadStackMergeTest.kt:68)\n" + + "\t(Coroutine creation stacktrace)\n" + + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)", + ignoredCoroutine = ":BlockingCoroutine" + ) + coroutineBlocker.await() + } + + private fun awaitCoroutineStarted() { + testMainBlocker.await() + while (coroutineBlocker.numberWaiting != 1) { + Thread.sleep(10) + } + } + + private fun CoroutineScope.launchCoroutine() { + launch(Dispatchers.Default) { + suspendingFunction() + assertTrue(true) + } + } + + private suspend fun suspendingFunction() { + // Typical use-case + withContext(Dispatchers.IO) { + yield() + nonSuspendingFun() + } + + assertTrue(true) + } + + private fun nonSuspendingFun() { + testMainBlocker.countDown() + coroutineBlocker.await() + } + + @Test + fun testStackMergeEscapeSuspendMethod() = runTest { + launchEscapingCoroutine() + awaitCoroutineStarted() + verifyDump( + "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored + "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" + + "\tat sun.misc.Unsafe.park(Native Method)\n" + + "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" + + "\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" + + "\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" + + "\tat java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.nonSuspendingFun(RunningThreadStackMergeTest.kt:83)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.access\$nonSuspendingFun(RunningThreadStackMergeTest.kt:12)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$suspendingFunctionWithContext\$2.invokeSuspend(RunningThreadStackMergeTest.kt:124)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.suspendingFunctionWithContext(RunningThreadStackMergeTest.kt:122)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchEscapingCoroutine\$1.invokeSuspend(RunningThreadStackMergeTest.kt:116)\n" + + "\t(Coroutine creation stacktrace)\n" + + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)", + ignoredCoroutine = ":BlockingCoroutine" + ) + coroutineBlocker.await() + } + + private fun CoroutineScope.launchEscapingCoroutine() { + launch(Dispatchers.Default) { + suspendingFunctionWithContext() + assertTrue(true) + } + } + + private suspend fun suspendingFunctionWithContext() { + withContext(Dispatchers.IO) { + actualSuspensionPoint() + nonSuspendingFun() + } + + assertTrue(true) + } + + @Test + fun testMergeThroughInvokeSuspend() = runTest { + launchEscapingCoroutineWithoutContext() + awaitCoroutineStarted() + verifyDump( + "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored + "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" + + "\tat sun.misc.Unsafe.park(Native Method)\n" + + "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" + + "\tat java.util.concurrent.locks.AbstractQueuedSynchronizer\$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)\n" + + "\tat java.util.concurrent.CyclicBarrier.dowait(CyclicBarrier.java:234)\n" + + "\tat java.util.concurrent.CyclicBarrier.await(CyclicBarrier.java:362)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.nonSuspendingFun(RunningThreadStackMergeTest.kt:83)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.suspendingFunctionWithoutContext(RunningThreadStackMergeTest.kt:160)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchEscapingCoroutineWithoutContext\$1.invokeSuspend(RunningThreadStackMergeTest.kt:153)\n" + + "\t(Coroutine creation stacktrace)\n" + + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)", + ignoredCoroutine = ":BlockingCoroutine" + ) + coroutineBlocker.await() + } + + private fun CoroutineScope.launchEscapingCoroutineWithoutContext() { + launch(Dispatchers.Default) { + suspendingFunctionWithoutContext() + assertTrue(true) + } + } + + private suspend fun suspendingFunctionWithoutContext() { + actualSuspensionPoint() + nonSuspendingFun() + assertTrue(true) + } + + @Test + fun testRunBlocking() = runBlocking { + verifyDump("Coroutine \"coroutine#1\":BlockingCoroutine{Active}@4bcd176c, state: RUNNING\n" + + "\tat java.lang.Thread.getStackTrace(Thread.java:1552)\n" + + "\tat kotlinx.coroutines.debug.internal.DebugProbesImpl.enhanceStackTraceWithThreadDump(DebugProbesImpl.kt:147)\n" + + "\tat kotlinx.coroutines.debug.internal.DebugProbesImpl.dumpCoroutines(DebugProbesImpl.kt:122)\n" + + "\tat kotlinx.coroutines.debug.internal.DebugProbesImpl.dumpCoroutines(DebugProbesImpl.kt:109)\n" + + "\tat kotlinx.coroutines.debug.DebugProbes.dumpCoroutines(DebugProbes.kt:122)\n" + + "\tat kotlinx.coroutines.debug.StracktraceUtilsKt.verifyDump(StracktraceUtils.kt)\n" + + "\tat kotlinx.coroutines.debug.StracktraceUtilsKt.verifyDump\$default(StracktraceUtils.kt)\n" + + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$testRunBlocking\$1.invokeSuspend(RunningThreadStackMergeTest.kt:112)\n" + + "\t(Coroutine creation stacktrace)\n" + + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n") + } + + + private suspend fun actualSuspensionPoint() { + nestedSuspensionPoint() + assertTrue(true) + } + + private suspend fun nestedSuspensionPoint() { + yield() + assertTrue(true) + } +} diff --git a/core/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt b/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt similarity index 67% rename from core/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt rename to kotlinx-coroutines-debug/test/SanitizedProbesTest.kt index 925f2f7219..3ee80ad38f 100644 --- a/core/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt +++ b/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt @@ -7,26 +7,17 @@ package definitely.not.kotlinx.coroutines import kotlinx.coroutines.* import kotlinx.coroutines.debug.* +import kotlinx.coroutines.selects.* import org.junit.* import org.junit.Test import java.util.concurrent.* import kotlin.test.* -class SanitizedProbesTest : TestBase() { +class SanitizedProbesTest : DebugTestBase() { @Before - fun setUp() { - before() + override fun setUp() { + super.setUp() DebugProbes.sanitizeStackTraces = true - DebugProbes.install() - } - - @After - fun tearDown() { - try { - DebugProbes.uninstall() - } finally { - onCompletion() - } } @Test @@ -58,8 +49,8 @@ class SanitizedProbesTest : TestBase() { val deferred = createActiveDeferred() yield() verifyDump( - "Coroutine \"coroutine#3\":BlockingCoroutine{Active}@7d68ef40, state: RUNNING (Last suspension stacktrace, not an actual stacktrace)\n" + - "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$testCoroutinesDump\$1.invokeSuspend(SanitizedProbesTest.kt:58)\n" + + "Coroutine \"coroutine#3\":BlockingCoroutine{Active}@7d68ef40, state: RUNNING\n" + + "\tat java.lang.Thread.getStackTrace(Thread.java)\n" + "\t(Coroutine creation stacktrace)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" + @@ -83,6 +74,48 @@ class SanitizedProbesTest : TestBase() { deferred.cancelAndJoin() } + @Test + fun testSelectBuilder() = runTest { + val selector = launchSelector() + expect(1) + yield() + expect(3) + verifyDump("Coroutine \"coroutine#1\":BlockingCoroutine{Active}@35fc6dc4, state: RUNNING\n" + + "\tat java.lang.Thread.getStackTrace(Thread.java:1552)\n" + // Skip the rest + "\t(Coroutine creation stacktrace)\n" + + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)", + + "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@1b68b9a4, state: SUSPENDED\n" + + "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$launchSelector\$1\$1\$1.invokeSuspend(SanitizedProbesTest.kt:105)\n" + + "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$launchSelector\$1.invokeSuspend(SanitizedProbesTest.kt:143)\n" + + "\t(Coroutine creation stacktrace)\n" + + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:25)\n" + + "\tat kotlinx.coroutines.BuildersKt.launch\$default(Unknown Source)\n" + + "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.launchSelector(SanitizedProbesTest.kt:100)\n" + + "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.access\$launchSelector(SanitizedProbesTest.kt:16)\n" + + "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$testSelectBuilder\$1.invokeSuspend(SanitizedProbesTest.kt:89)\n" + + "\tat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)\n" + + "\tat kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)\n" + + "\tat kotlinx.coroutines.TestBase.runTest\$default(TestBase.kt:154)\n" + + "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.testSelectBuilder(SanitizedProbesTest.kt:88)") + finish(4) + selector.cancelAndJoin() + } + + private fun CoroutineScope.launchSelector(): Job { + val job = CompletableDeferred(Unit) + return launch { + select { + job.onJoin { + expect(2) + delay(Long.MAX_VALUE) + 1 + } + } + } + } + private fun CoroutineScope.createActiveDeferred(): Deferred<*> = async { suspendingMethod() assertTrue(true) diff --git a/core/kotlinx-coroutines-debug/test/ScopedBuildersTest.kt b/kotlinx-coroutines-debug/test/ScopedBuildersTest.kt similarity index 71% rename from core/kotlinx-coroutines-debug/test/ScopedBuildersTest.kt rename to kotlinx-coroutines-debug/test/ScopedBuildersTest.kt index d0657d7a5b..c762725569 100644 --- a/core/kotlinx-coroutines-debug/test/ScopedBuildersTest.kt +++ b/kotlinx-coroutines-debug/test/ScopedBuildersTest.kt @@ -8,22 +8,7 @@ import kotlinx.coroutines.* import org.junit.* import kotlin.coroutines.* -class ScopedBuildersTest : TestBase() { - @Before - fun setUp() { - before() - DebugProbes.sanitizeStackTraces = false - DebugProbes.install() - } - - @After - fun tearDown() { - try { - DebugProbes.uninstall() - } finally { - onCompletion() - } - } +class ScopedBuildersTest : DebugTestBase() { @Test fun testNestedScopes() = runBlocking { @@ -31,10 +16,9 @@ class ScopedBuildersTest : TestBase() { yield() yield() verifyDump( - "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@16612a51, state: RUNNING (Last suspension stacktrace, not an actual stacktrace)\n" + - "\tat kotlinx.coroutines.debug.ScopedBuildersTest\$testNestedScopes\$1.invokeSuspend(ScopedBuildersTest.kt:32)\n" + + "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@16612a51, state: RUNNING\n" + "\t(Coroutine creation stacktrace)\n" + - "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n", + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n", "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@6b53e23f, state: SUSPENDED\n" + "\tat kotlinx.coroutines.debug.ScopedBuildersTest\$doWithContext\$2.invokeSuspend(ScopedBuildersTest.kt:49)\n" + @@ -61,4 +45,4 @@ class ScopedBuildersTest : TestBase() { } expectUnreached() } -} \ No newline at end of file +} diff --git a/core/kotlinx-coroutines-debug/test/StartModeProbesTest.kt b/kotlinx-coroutines-debug/test/StartModeProbesTest.kt similarity index 92% rename from core/kotlinx-coroutines-debug/test/StartModeProbesTest.kt rename to kotlinx-coroutines-debug/test/StartModeProbesTest.kt index bd33c5c9d2..a0297da396 100644 --- a/core/kotlinx-coroutines-debug/test/StartModeProbesTest.kt +++ b/kotlinx-coroutines-debug/test/StartModeProbesTest.kt @@ -9,23 +9,7 @@ import org.junit.* import org.junit.Test import kotlin.test.* -class StartModeProbesTest : TestBase() { - - @Before - fun setUp() { - before() - DebugProbes.sanitizeStackTraces = false - DebugProbes.install() - } - - @After - fun tearDown() { - try { - DebugProbes.uninstall() - } finally { - onCompletion() - } - } +class StartModeProbesTest : DebugTestBase() { @Test fun testUndispatched() = runTest { diff --git a/core/kotlinx-coroutines-debug/test/StracktraceUtils.kt b/kotlinx-coroutines-debug/test/StracktraceUtils.kt similarity index 94% rename from core/kotlinx-coroutines-debug/test/StracktraceUtils.kt rename to kotlinx-coroutines-debug/test/StracktraceUtils.kt index baa48b038d..cab4ed86b3 100644 --- a/core/kotlinx-coroutines-debug/test/StracktraceUtils.kt +++ b/kotlinx-coroutines-debug/test/StracktraceUtils.kt @@ -55,7 +55,7 @@ public fun toStackTrace(t: Throwable): String { public fun String.count(substring: String): Int = split(substring).size - 1 -public fun verifyDump(vararg traces: String) { +public fun verifyDump(vararg traces: String, ignoredCoroutine: String? = null) { val baos = ByteArrayOutputStream() DebugProbes.dumpCoroutines(PrintStream(baos)) val trace = baos.toString().split("\n\n") @@ -66,6 +66,10 @@ public fun verifyDump(vararg traces: String) { } // Drop "Coroutine dump" line trace.withIndex().drop(1).forEach { (index, value) -> + if (ignoredCoroutine != null && value.contains(ignoredCoroutine)) { + return@forEach + } + val expected = traces[index - 1].applyBackspace().split("\n\t(Coroutine creation stacktrace)\n", limit = 2) val actual = value.applyBackspace().split("\n\t(Coroutine creation stacktrace)\n", limit = 2) assertEquals(expected.size, actual.size) @@ -93,7 +97,7 @@ public fun verifyPartialDump(createdCoroutinesCount: Int, vararg frames: String) trace.any { tr -> tr.contains(frame) } } - assertEquals(createdCoroutinesCount, DebugProbes.dumpCoroutinesState().size) + assertEquals(createdCoroutinesCount, DebugProbes.dumpCoroutinesInfo().size) assertTrue(matches) } diff --git a/kotlinx-coroutines-debug/test/TestRuleExample.kt b/kotlinx-coroutines-debug/test/TestRuleExample.kt new file mode 100644 index 0000000000..b5d1c262bf --- /dev/null +++ b/kotlinx-coroutines-debug/test/TestRuleExample.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +import kotlinx.coroutines.* +import kotlinx.coroutines.debug.junit4.* +import org.junit.* + +@Ignore // do not run it on CI +class TestRuleExample { + + @JvmField + @Rule + public val timeout = CoroutinesTimeout.seconds(1) + + private suspend fun someFunctionDeepInTheStack() { + withContext(Dispatchers.IO) { + delay(Long.MAX_VALUE) + println("This line is never executed") + } + + println("This line is never executed as well") + } + + @Test + fun hangingTest() = runBlocking { + val job = launch { + someFunctionDeepInTheStack() + } + + println("Doing some work...") + job.join() + } + + @Test + fun successfulTest() = runBlocking { + launch { + delay(10) + }.join() + } + +} diff --git a/core/kotlinx-coroutines-debug/test/ToStringTest.kt b/kotlinx-coroutines-debug/test/ToStringTest.kt similarity index 70% rename from core/kotlinx-coroutines-debug/test/ToStringTest.kt rename to kotlinx-coroutines-debug/test/ToStringTest.kt index d311ae98cb..0a9e84efad 100644 --- a/core/kotlinx-coroutines-debug/test/ToStringTest.kt +++ b/kotlinx-coroutines-debug/test/ToStringTest.kt @@ -29,6 +29,50 @@ class ToStringTest : TestBase() { } } + + private suspend fun CoroutineScope.launchNestedScopes(): Job { + return launch { + expect(1) + coroutineScope { + expect(2) + launchDelayed() + + supervisorScope { + expect(3) + launchDelayed() + } + } + } + } + + private fun CoroutineScope.launchDelayed(): Job { + return launch { + delay(Long.MAX_VALUE) + } + } + + @Test + fun testPrintHierarchyWithScopes() = runBlocking { + val tab = '\t' + val expectedString = """ + "coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchNestedScopes$2$1.invokeSuspend(ToStringTest.kt) + $tab"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchDelayed$1.invokeSuspend(ToStringTest.kt) + $tab"coroutine":StandaloneCoroutine{Active}, continuation is SUSPENDED at line ToStringTest${'$'}launchDelayed$1.invokeSuspend(ToStringTest.kt) + """.trimIndent() + + val job = launchNestedScopes() + try { + repeat(5) { yield() } + val expected = expectedString.trimStackTrace().trimPackage() + expect(4) + assertEquals(expected, DebugProbes.jobToString(job).trimEnd().trimStackTrace().trimPackage()) + assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(job)).trimEnd().trimStackTrace().trimPackage()) + } finally { + finish(5) + job.cancelAndJoin() + } + } + @Test fun testCompletingHierarchy() = runBlocking { val tab = '\t' @@ -62,8 +106,7 @@ class ToStringTest : TestBase() { assertEquals(expected, DebugProbes.jobToString(root).trimEnd().trimStackTrace().trimPackage()) assertEquals(expected, DebugProbes.scopeToString(CoroutineScope(root)).trimEnd().trimStackTrace().trimPackage()) - root.cancel() - root.join() + root.cancelAndJoin() finish(7) } diff --git a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt new file mode 100644 index 0000000000..8d50c723cc --- /dev/null +++ b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.debug.junit4 + +import junit4.* +import kotlinx.coroutines.* +import org.junit.* +import org.junit.runners.model.* + +class CoroutinesTimeoutTest : TestBase() { + + @Rule + @JvmField + public val validation = TestFailureValidation( + 1000, false, + TestResultSpec("throwingTest", error = RuntimeException::class.java), + TestResultSpec("successfulTest"), + TestResultSpec( + "hangingTest", expectedOutParts = listOf( + "Coroutines dump", + "Test hangingTest timed out after 1 seconds", + "BlockingCoroutine{Active}", + "runBlocking", + "at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutTest.suspendForever", + "at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutTest\$hangingTest\$1.invokeSuspend"), + notExpectedOutParts = listOf("delay", "throwingTest"), + error = TestTimedOutException::class.java) + ) + + @Test + fun hangingTest() = runBlocking { + suspendForever() + expectUnreached() + } + + private suspend fun suspendForever() { + delay(Long.MAX_VALUE) + expectUnreached() + } + + @Test + fun throwingTest() = runBlocking { + throw RuntimeException() + } + + @Test + fun successfulTest() = runBlocking { + val job = launch { + yield() + } + + job.join() + } +} diff --git a/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt b/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt new file mode 100644 index 0000000000..9084926993 --- /dev/null +++ b/kotlinx-coroutines-debug/test/junit4/TestFailureValidation.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package junit4 + +import kotlinx.coroutines.debug.* +import kotlinx.coroutines.debug.junit4.* +import org.junit.rules.* +import org.junit.runner.* +import org.junit.runners.model.* +import java.io.* +import kotlin.test.* + +internal fun TestFailureValidation(timeoutMs: Long, cancelOnTimeout: Boolean, vararg specs: TestResultSpec): RuleChain = + RuleChain + .outerRule(TestFailureValidation(specs.associateBy { it.testName })) + .around( + CoroutinesTimeout( + timeoutMs, + cancelOnTimeout + ) + ) + +/** + * Rule that captures test result, serr and sout and validates it against provided [testsSpec] + */ +internal class TestFailureValidation(private val testsSpec: Map) : TestRule { + + companion object { + init { + DebugProbes.sanitizeStackTraces = false + } + } + override fun apply(base: Statement, description: Description): Statement { + return TestFailureStatement(base, description) + } + + inner class TestFailureStatement(private val test: Statement, private val description: Description) : Statement() { + private lateinit var sout: PrintStream + private lateinit var serr: PrintStream + private val capturedOut = ByteArrayOutputStream() + + override fun evaluate() { + try { + replaceOut() + test.evaluate() + } catch (e: Throwable) { + validateFailure(e) + return + } finally { + resetOut() + } + + validateSuccess() // To avoid falling into catch + } + + private fun validateSuccess() { + val spec = testsSpec[description.methodName] ?: error("Test spec not found: ${description.methodName}") + require(spec.error == null) { "Expected exception of type ${spec.error}, but test successfully passed" } + + val captured = capturedOut.toString() + assertFalse(captured.contains("Coroutines dump")) + assertTrue(captured.isEmpty(), captured) + } + + private fun validateFailure(e: Throwable) { + val spec = testsSpec[description.methodName] ?: error("Test spec not found: ${description.methodName}") + if (spec.error == null || !spec.error.isInstance(e)) { + throw IllegalStateException("Unexpected failure, expected ${spec.error}, had ${e::class}", e) + } + + if (e !is TestTimedOutException) return + + val captured = capturedOut.toString() + assertTrue(captured.contains("Coroutines dump")) + for (part in spec.expectedOutParts) { + assertTrue(captured.contains(part), "Expected $part to be part of the\n$captured") + } + + for (part in spec.notExpectedOutParts) { + assertFalse(captured.contains(part), "Expected $part not to be part of the\n$captured") + } + } + + private fun replaceOut() { + sout = System.out + serr = System.err + + System.setOut(PrintStream(capturedOut)) + System.setErr(PrintStream(capturedOut)) + } + + private fun resetOut() { + System.setOut(sout) + System.setErr(serr) + } + } +} + +data class TestResultSpec( + val testName: String, val expectedOutParts: List = listOf(), val notExpectedOutParts: + List = listOf(), val error: Class? = null +) diff --git a/core/kotlinx-coroutines-test/README.md b/kotlinx-coroutines-test/README.md similarity index 99% rename from core/kotlinx-coroutines-test/README.md rename to kotlinx-coroutines-test/README.md index a461bf4bea..8259e891ef 100644 --- a/core/kotlinx-coroutines-test/README.md +++ b/kotlinx-coroutines-test/README.md @@ -7,7 +7,7 @@ Test utilities for `kotlinx.coroutines`. Provides `Dispatchers.setMain` to overr Add `kotlinx-coroutines-test` to your project test dependencies: ``` dependencies { - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.1.1' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.2.0-alpha' } ``` diff --git a/core/kotlinx-coroutines-test/build.gradle b/kotlinx-coroutines-test/build.gradle similarity index 100% rename from core/kotlinx-coroutines-test/build.gradle rename to kotlinx-coroutines-test/build.gradle diff --git a/core/kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro b/kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro similarity index 100% rename from core/kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro rename to kotlinx-coroutines-test/resources/META-INF/proguard/coroutines.pro diff --git a/core/kotlinx-coroutines-test/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory b/kotlinx-coroutines-test/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory similarity index 100% rename from core/kotlinx-coroutines-test/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory rename to kotlinx-coroutines-test/resources/META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory diff --git a/core/kotlinx-coroutines-test/src/TestDispatchers.kt b/kotlinx-coroutines-test/src/TestDispatchers.kt similarity index 100% rename from core/kotlinx-coroutines-test/src/TestDispatchers.kt rename to kotlinx-coroutines-test/src/TestDispatchers.kt diff --git a/core/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt b/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt similarity index 98% rename from core/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt rename to kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt index 901a929558..bb52999d08 100644 --- a/core/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt +++ b/kotlinx-coroutines-test/src/internal/MainTestDispatcher.kt @@ -28,7 +28,6 @@ internal class TestMainDispatcher(private val mainFactory: MainDispatcherFactory @Suppress("INVISIBLE_MEMBER") private val delay: Delay get() = delegate as? Delay ?: DefaultDelay - @ExperimentalCoroutinesApi override val immediate: MainCoroutineDispatcher get() = (delegate as? MainCoroutineDispatcher)?.immediate ?: this diff --git a/core/kotlinx-coroutines-test/test/TestDispatchersTest.kt b/kotlinx-coroutines-test/test/TestDispatchersTest.kt similarity index 100% rename from core/kotlinx-coroutines-test/test/TestDispatchersTest.kt rename to kotlinx-coroutines-test/test/TestDispatchersTest.kt diff --git a/native/README.md b/native/README.md deleted file mode 100644 index 031b4336e1..0000000000 --- a/native/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Coroutines core for Kotlin/Native - -This directory contains modules that provide core coroutines support on Kotlin/Native. - -## Using in your projects - -Use [`org.jetbrains.kotlinx:kotlinx-coroutines-core-native:`](kotlinx-coroutines-core-native/README.md) -module in your Gradle/Maven dependencies. -Only single-threaded code (JS-style) is currently supported. - -Kotlin/Native libraries would only work under Gradle version 4.7 (**exactly**) -and you should use `kotlin-platform-native` plugin. - -First of all, you'll need to enable Gradle metadata in your -`settings.gradle` file: - -```groovy -enableFeaturePreview('GRADLE_METADATA') -``` - -Then, you'll need to apply the corresponding plugin and add appropriate dependencies in your -`build.gradle` file: - -```groovy -buildscript { - repositories { - jcenter() - maven { url 'https://plugins.gradle.org/m2/' } - maven { url 'https://dl.bintray.com/jetbrains/kotlin-native-dependencies' } - } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:$kotlin_native_version" - } - -} - -apply plugin: 'kotlin-platform-native' - -repositories { - jcenter() -} - -dependencies { - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.1.1' -} - -sourceSets { - main { - component { - targets = ["ios_arm64", "ios_arm32", "ios_x64", "macos_x64", "linux_x64", "mingw_x64"] - outputKinds = [EXECUTABLE] - } - } -} -``` - -Since Kotlin/Native does not generally provide binary compatibility between versions, -you should use the same version of Kotlin/Native compiler as was used to build `kotlinx.coroutines`. -Add an appropriate `kotlin_native_version` to your `gradle.properties` file. -See [gradle.properties](../gradle.properties). - diff --git a/reactive/README.md b/reactive/README.md index 08ff862df1..8679a2b078 100644 --- a/reactive/README.md +++ b/reactive/README.md @@ -5,6 +5,6 @@ Module name below corresponds to the artifact name in Maven/Gradle. ## Modules -* [kotlinx-coroutines-reactive](kotlinx-coroutines-reactive/README.md) -- utilities for [Reactive Streams](http://www.reactive-streams.org) +* [kotlinx-coroutines-reactive](kotlinx-coroutines-reactive/README.md) -- utilities for [Reactive Streams](https://www.reactive-streams.org) * [kotlinx-coroutines-reactor](kotlinx-coroutines-reactor/README.md) -- utilities for [Reactor](https://projectreactor.io) * [kotlinx-coroutines-rx2](kotlinx-coroutines-rx2/README.md) -- utilities for [RxJava 2.x](https://github.com/ReactiveX/RxJava) diff --git a/reactive/coroutines-guide-reactive.md b/reactive/coroutines-guide-reactive.md index 8935d4a74b..b9114da9bf 100644 --- a/reactive/coroutines-guide-reactive.md +++ b/reactive/coroutines-guide-reactive.md @@ -28,11 +28,11 @@ a better introduction into the world of coroutines. There are several modules in `kotlinx.coroutines` project that are related to reactive streams: -* [kotlinx-coroutines-reactive](kotlinx-coroutines-reactive) -- utilities for [Reactive Streams](http://www.reactive-streams.org) +* [kotlinx-coroutines-reactive](kotlinx-coroutines-reactive) -- utilities for [Reactive Streams](https://www.reactive-streams.org) * [kotlinx-coroutines-reactor](kotlinx-coroutines-reactor) -- utilities for [Reactor](https://projectreactor.io) * [kotlinx-coroutines-rx2](kotlinx-coroutines-rx2) -- utilities for [RxJava 2.x](https://github.com/ReactiveX/RxJava) -This guide is mostly based on [Reactive Streams](http://www.reactive-streams.org) specification and uses +This guide is mostly based on [Reactive Streams](https://www.reactive-streams.org) specification and uses its `Publisher` interface with some examples based on [RxJava 2.x](https://github.com/ReactiveX/RxJava), which implements reactive streams specification. @@ -75,8 +75,8 @@ This section outlines key differences between reactive streams and coroutine-bas The [Channel] is somewhat similar concept to the following reactive stream classes: * Reactive stream [Publisher](https://github.com/reactive-streams/reactive-streams-jvm/blob/master/api/src/main/java/org/reactivestreams/Publisher.java); -* Rx Java 1.x [Observable](http://reactivex.io/RxJava/javadoc/rx/Observable.html); -* Rx Java 2.x [Flowable](http://reactivex.io/RxJava/2.x/javadoc/), which implements `Publisher`. +* Rx Java 1.x [Observable](https://reactivex.io/RxJava/javadoc/rx/Observable.html); +* Rx Java 2.x [Flowable](https://reactivex.io/RxJava/2.x/javadoc/), which implements `Publisher`. They all describe an asynchronous stream of elements (aka items in Rx), either infinite or finite, and all of them support backpressure. @@ -140,7 +140,7 @@ from it again cannot receive anything. Let us rewrite this code using [publish] coroutine builder from `kotlinx-coroutines-reactive` module instead of [produce] from `kotlinx-coroutines-core` module. The code stays the same, but where `source` used to have [ReceiveChannel] type, it now has reactive streams -[Publisher](http://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/org/reactivestreams/Publisher.html) +[Publisher](https://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/org/reactivestreams/Publisher.html) type. Here we've used Rx -[subscribe](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#subscribe(io.reactivex.functions.Consumer)) +[subscribe](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#subscribe(io.reactivex.functions.Consumer)) operator that does not have its own scheduler and operates on the same thread that the publisher -- on a default shared pool of threads in this example. ### Rx observeOn In Rx you use special operators to modify the threading context for operations in the chain. You -can find some [good guides](http://tomstechnicalblog.blogspot.ru/2016/02/rxjava-understanding-observeon-and.html) +can find some [good guides](https://tomstechnicalblog.blogspot.ru/2016/02/rxjava-understanding-observeon-and.html) about them, if you are not familiar. For example, there is -[observeOn](http://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#observeOn(io.reactivex.Scheduler)) +[observeOn](https://reactivex.io/RxJava/2.x/javadoc/io/reactivex/Flowable.html#observeOn(io.reactivex.Scheduler)) operator. Let us modify the previous example to observe using `Schedulers.computation()`: