{
-> 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 |
+| - | - |
+|  |  |
+
+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