diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 62fd5c7dfd..461a31ed76 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -12,4 +12,4 @@
-
\ No newline at end of file
+
diff --git a/CHANGES.md b/CHANGES.md
index acf2d2c28b..c59e3b306e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,78 @@
# Change log for kotlinx.coroutines
+## Version 1.7.0
+
+### Core API significant improvements
+
+* New `Channel` implementation with significant performance improvements across the API (#3621).
+* New `select` operator implementation: faster, more lightweight, and more robust (#3020).
+* `Mutex` and `Semaphore` now share the same underlying data structure (#3020).
+* `Dispatchers.IO` is added to K/N (#3205)
+ * `newFixedThreadPool` and `Dispatchers.Default` implementations on K/N were wholly rewritten to support graceful growth under load (#3595).
+* `kotlinx-coroutines-test` rework:
+ - Add the `timeout` parameter to `runTest` for the whole-test timeout, 10 seconds by default (#3270). This replaces the configuration of quiescence timeouts, which is now deprecated (#3603).
+ - The `withTimeout` exception messages indicate if the timeout used the virtual time (#3588).
+ - `TestCoroutineScheduler`, `runTest`, and `TestScope` API are promoted to stable (#3622).
+ - `runTest` now also fails if there were uncaught exceptions in coroutines not inherited from the test coroutine (#1205).
+
+### Breaking changes
+
+* Old K/N memory model is no longer supported (#3375).
+* New generic upper bounds were added to reactive integration API where the language since 1.8.0 dictates (#3393).
+* `kotlinx-coroutines-core` and `kotlinx-coroutines-jdk8` artifacts were merged into a single artifact (#3268).
+* Artificial stackframes in stacktrace recovery no longer contain the `\b` symbol and are now navigable in IDE and supplied with proper documentation (#2291).
+* `CoroutineContext.isActive` returns `true` for contexts without any job in them (#3300).
+
+### Bug fixes and improvements
+
+* Kotlin version is updated to 1.8.20
+* Atomicfu version is updated to 0.20.2.
+* `JavaFx` version is updated to 17.0.2 in `kotlinx-coroutines-javafx` (#3671)..
+* JPMS is supported (#2237). Thanks @lion7!
+* `BroadcastChannel` and all the corresponding API are deprecated (#2680).
+* Added all supported K/N targets (#3601, #812, #855).
+* K/N `Dispatchers.Default` is backed by the number of threads equal to the number of available cores (#3366).
+* Fixed an issue where some coroutines' internal exceptions were not properly serializable (#3328).
+* Introduced `Job.parent` API (#3201).
+* Fixed a bug when `TestScheduler` leaked cancelled jobs (#3398).
+* `TestScope.timeSource` now provides comparable time marks (#3617). Thanks @hfhbd!
+* Fixed an issue when cancelled `withTimeout` handles were preserved in JS runtime (#3440).
+* Ensure `awaitFrame` only awaits a single frame when used from the main looper (#3432). Thanks @pablobaxter!
+* Obsolete `Class-Path` attribute was removed from `kotlinx-coroutines-debug.jar` manifest (#3361).
+* Fixed a bug when `updateThreadContext` operated on the parent context (#3411).
+* Added new `Flow.filterIsInstance` extension (#3240).
+* `Dispatchers.Default` thread name prefixes are now configurable with system property (#3231).
+* Added `Flow.timeout` operator as `@FlowPreview` (#2624). Thanks @pablobaxter!
+* Improved the performance of the `future` builder in case of exceptions (#3475). Thanks @He-Pin!
+* `Mono.awaitSingleOrNull` now waits for the `onComplete` signal (#3487).
+* `Channel.isClosedForSend` and `Channel.isClosedForReceive` are promoted from experimental to delicate (#3448).
+* Fixed a data race in native `EventLoop` (#3547).
+* `Dispatchers.IO.limitedParallelism(valueLargerThanIOSize)` no longer creates an additional wrapper (#3442). Thanks @dovchinnikov!
+* Various `@FlowPreview` and `@ExperimentalCoroutinesApi` are promoted to experimental and stable respectively (#3542, #3097, #3548).
+* Performance improvements in `Dispatchers.Default` and `Dispatchers.IO` (#3416, #3418).
+* Fixed a bug when internal `suspendCancellableCoroutineReusable` might have hanged (#3613).
+* Introduced internal API to process events in the current system dispatcher (#3439).
+* Global `CoroutineExceptionHandler` is no longer invoked in case of unprocessed `future` failure (#3452).
+* Performance improvements and reduced thread-local pressure for the `withContext` operator (#3592).
+* Improved performance of `DebugProbes` (#3527).
+* Fixed a bug when the coroutine debugger might have detected the state of a coroutine incorrectly (#3193).
+* `CoroutineDispatcher.asExecutor()` runs tasks without dispatching if the dispatcher is unconfined (#3683). Thanks @odedniv!
+* `SharedFlow.toMutableList` and `SharedFlow.toSet` lints are introduced (#3706).
+* `Channel.invokeOnClose` is promoted to stable API (#3358).
+* Improved lock contention in `Dispatchers.Default` and `Dispatchers.IO` during the startup phase (#3652).
+* Fixed a bug that led to threads oversubscription in `Dispatchers.Default` (#3642).
+* Fixed a bug that allowed `limitedParallelism` to perform dispatches even after the underlying dispatcher was closed (#3672).
+* Fixed a bug that prevented stacktrace recovery when the exception's constructor from `cause` was selected (#3714).
+* Improved sanitizing of stracktrace-recovered traces (#3714).
+* Introduced an internal flag to disable uncaught exceptions reporting in tests as a temporary migration mechanism (#3736).
+* Various documentation improvements and fixes.
+
+### Changelog relative to version 1.7.0-RC
+
+* Fixed a bug that prevented stacktrace recovery when the exception's constructor from `cause` was selected (#3714).
+* Improved sanitizing of stracktrace-recovered traces (#3714).
+* Introduced an internal flag to disable uncaught exceptions reporting in tests as a temporary migration mechanism (#3736).
+
## Version 1.7.0-RC
* Kotlin version is updated to 1.8.20.
diff --git a/README.md b/README.md
index 283afb85ba..dad7e02f52 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[![Kotlin Stable](https://kotl.in/badges/stable.svg)](https://kotlinlang.org/docs/components-stability.html)
[![JetBrains official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0)
-[![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.0-RC)](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.0-RC)
+[![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.0)](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.0)
[![Kotlin](https://img.shields.io/badge/kotlin-1.8.20-blue.svg?logo=kotlin)](http://kotlinlang.org)
[![Slack channel](https://img.shields.io/badge/chat-slack-green.svg?logo=slack)](https://kotlinlang.slack.com/messages/coroutines/)
@@ -85,7 +85,7 @@ Add dependencies (you can also add other modules that you need):
org.jetbrains.kotlinx
kotlinx-coroutines-core
- 1.7.0-RC
+ 1.7.0
```
@@ -103,7 +103,7 @@ Add dependencies (you can also add other modules that you need):
```kotlin
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-RC")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0")
}
```
@@ -133,7 +133,7 @@ Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android)
module as a dependency when using `kotlinx.coroutines` on Android:
```kotlin
-implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0-RC")
+implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0")
```
This gives you access to the Android [Dispatchers.Main]
@@ -168,7 +168,7 @@ In common code that should get compiled for different platforms, you can add a d
```kotlin
commonMain {
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-RC")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0")
}
}
```
@@ -180,7 +180,7 @@ Platform-specific dependencies are recommended to be used only for non-multiplat
#### JS
Kotlin/JS version of `kotlinx.coroutines` is published as
-[`kotlinx-coroutines-core-js`](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.7.0-RC)
+[`kotlinx-coroutines-core-js`](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.7.0)
(follow the link to get the dependency declaration snippet) and as [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotlinx-coroutines-core) NPM package.
#### Native
diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts
index b4629809db..e64f18905f 100644
--- a/benchmarks/build.gradle.kts
+++ b/benchmarks/build.gradle.kts
@@ -24,7 +24,7 @@ java {
tasks.named("compileJmhKotlin") {
kotlinOptions {
jvmTarget = "1.8"
- freeCompilerArgs += "-Xjvm-default=enable"
+ freeCompilerArgs += "-Xjvm-default=all"
}
}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt
index 006d36c04b..10433fcb45 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt
@@ -34,14 +34,12 @@ abstract class ShakespearePlaysScrabble {
public interface LongWrapper {
fun get(): Long
- @JvmDefault
fun incAndSet(): LongWrapper {
return object : LongWrapper {
override fun get(): Long = this@LongWrapper.get() + 1L
}
}
- @JvmDefault
fun add(other: LongWrapper): LongWrapper {
return object : LongWrapper {
override fun get(): Long = this@LongWrapper.get() + other.get()
diff --git a/gradle.properties b/gradle.properties
index 966a90daf6..2daed3cf4c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,7 +3,7 @@
#
# Kotlin
-version=1.7.0-RC-SNAPSHOT
+version=1.7.0-SNAPSHOT
group=org.jetbrains.kotlinx
kotlin_version=1.8.20
@@ -13,7 +13,7 @@ junit5_version=5.7.0
atomicfu_version=0.20.2
knit_version=0.4.0
html_version=0.7.2
-lincheck_version=2.16
+lincheck_version=2.17
dokka_version=1.8.10
byte_buddy_version=1.10.9
reactor_version=3.4.1
diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties
index 4644d2346a..ac155f1dac 100644
--- a/integration-testing/gradle.properties
+++ b/integration-testing/gradle.properties
@@ -1,5 +1,5 @@
kotlin_version=1.8.20
-coroutines_version=1.7.0-RC-SNAPSHOT
+coroutines_version=1.7.0-SNAPSHOT
asm_version=9.3
kotlin.code.style=official
diff --git a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt
index edb1c3c9f7..b749ee63f8 100644
--- a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt
@@ -1220,9 +1220,9 @@ internal open class BufferedChannel(
incCompletedExpandBufferAttempts()
return
}
- // Is `bufferEndSegment` outdated?
+ // Is `bufferEndSegment` outdated or is the segment with the required id already removed?
// Find the required segment, creating new ones if needed.
- if (segment.id < id) {
+ if (segment.id != id) {
segment = findSegmentBufferEnd(id, segment, b)
// Restart if the required segment is removed, or
// the linked list of segments is already closed,
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstructor.kt b/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstructor.kt
index de15225266..03308f6137 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstructor.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstructor.kt
@@ -32,42 +32,48 @@ internal fun tryCopyException(exception: E): E? {
private fun createConstructor(clz: Class): Ctor {
val nullResult: Ctor = { null } // Pre-cache class
- // Skip reflective copy if an exception has additional fields (that are usually populated in user-defined constructors)
+ // Skip reflective copy if an exception has additional fields (that are typically populated in user-defined constructors)
if (throwableFields != clz.fieldsCountOrDefault(0)) return nullResult
/*
- * 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.
- */
- val constructors = clz.constructors.sortedByDescending { it.parameterTypes.size }
- for (constructor in constructors) {
- val result = createSafeConstructor(constructor)
- if (result != null) return result
- }
- return nullResult
-}
-
-private fun createSafeConstructor(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
+ * Try to reflectively find constructor(message, cause), constructor(message), constructor(cause), or constructor(),
+ * in that order of priority.
+ * Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
+ *
+ * By default, Java's reflection iterates over ctors in the source-code order and the sorting is stable, so we can
+ * not rely on the order of iteration. Instead, we assign a unique priority to each ctor type.
+ */
+ return clz.constructors.map { constructor ->
+ val p = constructor.parameterTypes
+ when (p.size) {
+ 2 -> when {
+ p[0] == String::class.java && p[1] == Throwable::class.java ->
+ safeCtor { e -> constructor.newInstance(e.message, e) as Throwable } to 3
+ else -> null to -1
+ }
+ 1 -> when (p[0]) {
+ String::class.java ->
+ safeCtor { e -> (constructor.newInstance(e.message) as Throwable).also { it.initCause(e) } } to 2
+ Throwable::class.java ->
+ safeCtor { e -> constructor.newInstance(e) as Throwable } to 1
+ else -> null to -1
+ }
+ 0 -> safeCtor { e -> (constructor.newInstance() as Throwable).also { it.initCause(e) } } to 0
+ else -> null to -1
}
- 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
- }
+ }.maxByOrNull(Pair<*, Int>::second)?.first ?: nullResult
}
-private inline fun safeCtor(crossinline block: (Throwable) -> Throwable): Ctor =
- { e -> runCatching { block(e) }.getOrNull() }
+private fun safeCtor(block: (Throwable) -> Throwable): Ctor = { e ->
+ runCatching {
+ val result = block(e)
+ /*
+ * Verify that the new exception has the same message as the original one (bail out if not, see #1631)
+ * or if the new message complies the contract from `Throwable(cause).message` contract.
+ */
+ if (e.message != result.message && result.message != e.toString()) null
+ else result
+ }.getOrNull()
+}
private fun Class<*>.fieldsCountOrDefault(defaultValue: Int) =
kotlin.runCatching { fieldsCount() }.getOrDefault(defaultValue)
diff --git a/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
index 5193b0dc26..afc9989646 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
@@ -33,16 +33,16 @@ private val stackTraceRecoveryClassName = runCatching {
internal actual fun recoverStackTrace(exception: E): E {
if (!RECOVER_STACK_TRACES) return exception
// No unwrapping on continuation-less path: exception is not reported multiple times via slow paths
- val copy = tryCopyAndVerify(exception) ?: return exception
+ val copy = tryCopyException(exception) ?: return exception
return copy.sanitizeStackTrace()
}
private fun E.sanitizeStackTrace(): E {
val stackTrace = stackTrace
val size = stackTrace.size
- val lastIntrinsic = stackTrace.frameIndex(stackTraceRecoveryClassName)
+ val lastIntrinsic = stackTrace.indexOfLast { stackTraceRecoveryClassName == it.className }
val startIndex = lastIntrinsic + 1
- val endIndex = stackTrace.frameIndex(baseContinuationImplClassName)
+ val endIndex = stackTrace.firstFrameIndex(baseContinuationImplClassName)
val adjustment = if (endIndex == -1) 0 else size - endIndex
val trace = Array(size - lastIntrinsic - adjustment) {
if (it == 0) {
@@ -70,7 +70,7 @@ private fun recoverFromStackFrame(exception: E, continuation: Co
val (cause, recoveredStacktrace) = exception.causeAndStacktrace()
// Try to create an exception of the same type and get stacktrace from continuation
- val newException = tryCopyAndVerify(cause) ?: return exception
+ val newException = tryCopyException(cause) ?: return exception
// Update stacktrace
val stacktrace = createStackTrace(continuation)
if (stacktrace.isEmpty()) return exception
@@ -82,14 +82,6 @@ private fun recoverFromStackFrame(exception: E, continuation: Co
return createFinalException(cause, newException, stacktrace)
}
-private fun tryCopyAndVerify(exception: E): E? {
- val newException = tryCopyException(exception) ?: return null
- // Verify that the new exception has the same message as the original one (bail out if not, see #1631)
- // CopyableThrowable has control over its message and thus can modify it the way it wants
- if (exception !is CopyableThrowable<*> && newException.message != exception.message) return null
- return newException
-}
-
/*
* Here we partially copy original exception stackTrace to make current one much prettier.
* E.g. for
@@ -109,7 +101,7 @@ private fun tryCopyAndVerify(exception: E): E? {
private fun createFinalException(cause: E, result: E, resultStackTrace: ArrayDeque): E {
resultStackTrace.addFirst(ARTIFICIAL_FRAME)
val causeTrace = cause.stackTrace
- val size = causeTrace.frameIndex(baseContinuationImplClassName)
+ val size = causeTrace.firstFrameIndex(baseContinuationImplClassName)
if (size == -1) {
result.stackTrace = resultStackTrace.toTypedArray()
return result
@@ -157,7 +149,6 @@ private fun mergeRecoveredTraces(recoveredStacktrace: Array,
}
}
-@Suppress("NOTHING_TO_INLINE")
internal actual suspend inline fun recoverAndThrow(exception: Throwable): Nothing {
if (!RECOVER_STACK_TRACES) throw exception
suspendCoroutineUninterceptedOrReturn {
@@ -198,7 +189,7 @@ private fun createStackTrace(continuation: CoroutineStackFrame): ArrayDeque.frameIndex(methodName: String) = indexOfFirst { methodName == it.className }
+private fun Array.firstFrameIndex(methodName: String) = indexOfFirst { methodName == it.className }
private fun StackTraceElement.elementWiseEquals(e: StackTraceElement): Boolean {
/*
diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromChannel.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromChannel.txt
index 64085ad329..da3558ba30 100644
--- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromChannel.txt
+++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromChannel.txt
@@ -1,5 +1,4 @@
kotlinx.coroutines.RecoverableTestException
- at kotlinx.coroutines.internal.StackTraceRecoveryKt.recoverStackTrace(StackTraceRecovery.kt)
at kotlinx.coroutines.channels.BufferedChannel.receive$suspendImpl(BufferedChannel.kt)
at kotlinx.coroutines.channels.BufferedChannel.receive(BufferedChannel.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelReceive(StackTraceRecoveryChannelsTest.kt)
@@ -7,4 +6,4 @@ kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$channelReceive$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt)
Caused by: kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt)
- at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)
\ No newline at end of file
+ at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)
diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfined.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfined.txt
index a8461556d1..9fc7167833 100644
--- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfined.txt
+++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfined.txt
@@ -1,5 +1,4 @@
kotlinx.coroutines.RecoverableTestException
- at kotlinx.coroutines.internal.StackTraceRecoveryKt.recoverStackTrace(StackTraceRecovery.kt)
at kotlinx.coroutines.channels.BufferedChannel.receive$suspendImpl(BufferedChannel.kt)
at kotlinx.coroutines.channels.BufferedChannel.receive(BufferedChannel.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt)
@@ -7,4 +6,4 @@ Caused by: kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.access$testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testUnconfined$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt)
- at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)
\ No newline at end of file
+ at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)
diff --git a/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt
index bdb615d83c..da73ca6220 100644
--- a/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt
@@ -9,7 +9,7 @@ import org.jetbrains.kotlinx.lincheck.strategy.stress.*
import org.jetbrains.kotlinx.lincheck.verifier.*
import org.junit.*
-abstract class AbstractLincheckTest : VerifierState() {
+abstract class AbstractLincheckTest {
open fun > O.customize(isStressTest: Boolean): O = this
open fun ModelCheckingOptions.customize(isStressTest: Boolean): ModelCheckingOptions = this
open fun StressOptions.customize(isStressTest: Boolean): StressOptions = this
@@ -41,6 +41,4 @@ abstract class AbstractLincheckTest : VerifierState() {
.actorsPerThread(if (isStressTest) 3 else 2)
.actorsAfter(if (isStressTest) 3 else 0)
.customize(isStressTest)
-
- override fun extractState(): Any = error("Not implemented")
}
diff --git a/kotlinx-coroutines-core/jvm/test/MutexCancellationStressTest.kt b/kotlinx-coroutines-core/jvm/test/MutexCancellationStressTest.kt
index eb6360dac0..20798b837d 100644
--- a/kotlinx-coroutines-core/jvm/test/MutexCancellationStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/MutexCancellationStressTest.kt
@@ -8,7 +8,11 @@ import kotlinx.coroutines.internal.*
import kotlinx.coroutines.selects.*
import kotlinx.coroutines.sync.*
import org.junit.*
+import org.junit.Test
import java.util.concurrent.*
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.test.*
class MutexCancellationStressTest : TestBase() {
@Test
@@ -18,13 +22,16 @@ class MutexCancellationStressTest : TestBase() {
val mutexOwners = Array(mutexJobNumber) { "$it" }
val dispatcher = Executors.newFixedThreadPool(mutexJobNumber + 2).asCoroutineDispatcher()
var counter = 0
- val counterLocal = Array(mutexJobNumber) { LocalAtomicInt(0) }
- val completed = LocalAtomicInt(0)
+ val counterLocal = Array(mutexJobNumber) { AtomicInteger(0) }
+ val completed = AtomicBoolean(false)
val mutexJobLauncher: (jobNumber: Int) -> Job = { jobId ->
val coroutineName = "MutexJob-$jobId"
- launch(dispatcher + CoroutineName(coroutineName)) {
- while (completed.value == 0) {
+ // ATOMIC to always have a chance to proceed
+ launch(dispatcher + CoroutineName(coroutineName), CoroutineStart.ATOMIC) {
+ while (!completed.get()) {
+ // Stress out holdsLock
mutex.holdsLock(mutexOwners[(jobId + 1) % mutexJobNumber])
+ // Stress out lock-like primitives
if (mutex.tryLock(mutexOwners[jobId])) {
counterLocal[jobId].incrementAndGet()
counter++
@@ -47,18 +54,20 @@ class MutexCancellationStressTest : TestBase() {
val mutexJobs = (0 until mutexJobNumber).map { mutexJobLauncher(it) }.toMutableList()
val checkProgressJob = launch(dispatcher + CoroutineName("checkProgressJob")) {
var lastCounterLocalSnapshot = (0 until mutexJobNumber).map { 0 }
- while (completed.value == 0) {
- delay(1000)
+ while (!completed.get()) {
+ delay(500)
+ // If we've caught the completion after delay, then there is a chance no progress were made whatsoever, bail out
+ if (completed.get()) return@launch
val c = counterLocal.map { it.value }
for (i in 0 until mutexJobNumber) {
- assert(c[i] > lastCounterLocalSnapshot[i]) { "No progress in MutexJob-$i" }
+ assert(c[i] > lastCounterLocalSnapshot[i]) { "No progress in MutexJob-$i, last observed state: ${c[i]}" }
}
lastCounterLocalSnapshot = c
}
}
val cancellationJob = launch(dispatcher + CoroutineName("cancellationJob")) {
var cancellingJobId = 0
- while (completed.value == 0) {
+ while (!completed.get()) {
val jobToCancel = mutexJobs.removeFirst()
jobToCancel.cancelAndJoin()
mutexJobs += mutexJobLauncher(cancellingJobId)
@@ -66,11 +75,11 @@ class MutexCancellationStressTest : TestBase() {
}
}
delay(2000L * stressTestMultiplier)
- completed.value = 1
+ completed.set(true)
cancellationJob.join()
mutexJobs.forEach { it.join() }
checkProgressJob.join()
- check(counter == counterLocal.sumOf { it.value })
+ assertEquals(counter, counterLocal.sumOf { it.value })
dispatcher.close()
}
}
diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt
index d4e19040a5..0f987e56e0 100644
--- a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryCustomExceptionsTest.kt
@@ -87,6 +87,26 @@ class StackTraceRecoveryCustomExceptionsTest : TestBase() {
assertEquals("Token OK", ex.message)
}
+ @Test
+ fun testNestedExceptionWithCause() = runTest {
+ val result = runCatching {
+ coroutineScope {
+ throw NestedException(IllegalStateException("ERROR"))
+ }
+ }
+ val ex = result.exceptionOrNull() ?: error("Expected to fail")
+ assertIs(ex)
+ assertIs(ex.cause)
+ val originalCause = ex.cause?.cause
+ assertIs(originalCause)
+ assertEquals("ERROR", originalCause.message)
+ }
+
+ class NestedException : RuntimeException {
+ constructor(cause: Throwable) : super(cause)
+ constructor() : super()
+ }
+
@Test
fun testWrongMessageExceptionInChannel() = runTest {
val result = produce(SupervisorJob() + Dispatchers.Unconfined) {
diff --git a/kotlinx-coroutines-core/jvm/test/internal/OnDemandAllocatingPoolLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/internal/OnDemandAllocatingPoolLincheckTest.kt
index de9ab8d5cd..9655890c4f 100644
--- a/kotlinx-coroutines-core/jvm/test/internal/OnDemandAllocatingPoolLincheckTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/internal/OnDemandAllocatingPoolLincheckTest.kt
@@ -27,8 +27,6 @@ abstract class OnDemandAllocatingPoolLincheckTest(maxCapacity: Int) : AbstractLi
@Operation
fun close(): String = pool.close().sorted().toString()
-
- override fun extractState(): Any = pool.stateRepresentation()
}
abstract class OnDemandAllocatingSequentialPool(private val maxCapacity: Int) {
diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt
index 87ed74b715..092ef6fc52 100644
--- a/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt
@@ -16,7 +16,6 @@ import org.jetbrains.kotlinx.lincheck.annotations.*
import org.jetbrains.kotlinx.lincheck.annotations.Operation
import org.jetbrains.kotlinx.lincheck.paramgen.*
import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.*
-import org.jetbrains.kotlinx.lincheck.verifier.*
class RendezvousChannelLincheckTest : ChannelLincheckTestBaseWithOnSend(
c = Channel(RENDEZVOUS),
@@ -176,7 +175,7 @@ private class NumberedCancellationException(number: Int) : CancellationException
}
-abstract class SequentialIntChannelBase(private val capacity: Int) : VerifierState() {
+abstract class SequentialIntChannelBase(private val capacity: Int) {
private val senders = ArrayList, Int>>()
private val receivers = ArrayList>()
private val buffer = ArrayList()
@@ -266,8 +265,6 @@ abstract class SequentialIntChannelBase(private val capacity: Int) : VerifierSta
if (closedMessage !== null) return false
return buffer.isEmpty() && senders.isEmpty()
}
-
- override fun extractState() = buffer to closedMessage
}
private fun CancellableContinuation.resume(res: T): Boolean {
diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/LockFreeTaskQueueLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/LockFreeTaskQueueLincheckTest.kt
index 2a9164e1d7..11f5535b5a 100644
--- a/kotlinx-coroutines-core/jvm/test/lincheck/LockFreeTaskQueueLincheckTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/LockFreeTaskQueueLincheckTest.kt
@@ -30,8 +30,6 @@ internal abstract class AbstractLockFreeTaskQueueWithoutRemoveLincheckTest(
override fun > O.customize(isStressTest: Boolean): O =
verifier(QuiescentConsistencyVerifier::class.java)
- override fun extractState() = q.map { it } to q.isClosed()
-
override fun ModelCheckingOptions.customize(isStressTest: Boolean) =
checkObstructionFreedom()
}
@@ -42,9 +40,8 @@ internal class MCLockFreeTaskQueueWithRemoveLincheckTest : AbstractLockFreeTaskQ
fun removeFirstOrNull() = q.removeFirstOrNull()
}
-@OpGroupConfig(name = "consumer", nonParallel = true)
internal class SCLockFreeTaskQueueWithRemoveLincheckTest : AbstractLockFreeTaskQueueWithoutRemoveLincheckTest(singleConsumer = true) {
@QuiescentConsistent
- @Operation(group = "consumer")
+ @Operation(nonParallelGroup = "consumer")
fun removeFirstOrNull() = q.removeFirstOrNull()
}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt
index 6fd28e424e..983a64acda 100644
--- a/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt
@@ -40,8 +40,5 @@ class MutexLincheckTest : AbstractLincheckTest() {
override fun > O.customize(isStressTest: Boolean): O =
actorsBefore(0)
- // state[i] == true <=> mutex.holdsLock(i) with the only exception for 0 that specifies `null`.
- override fun extractState() = (1..2).map { mutex.holdsLock(it) } + mutex.isLocked
-
private val Int.asOwnerOrNull get() = if (this == 0) null else this
}
diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/ResizableAtomicArrayLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/ResizableAtomicArrayLincheckTest.kt
index 1948a78ecc..e937b37e08 100644
--- a/kotlinx-coroutines-core/jvm/test/lincheck/ResizableAtomicArrayLincheckTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/ResizableAtomicArrayLincheckTest.kt
@@ -11,17 +11,14 @@ import org.jetbrains.kotlinx.lincheck.paramgen.*
@Param(name = "index", gen = IntGen::class, conf = "0:4")
@Param(name = "value", gen = IntGen::class, conf = "1:5")
-@OpGroupConfig(name = "sync", nonParallel = true)
class ResizableAtomicArrayLincheckTest : AbstractLincheckTest() {
private val a = ResizableAtomicArray(2)
@Operation
fun get(@Param(name = "index") index: Int): Int? = a[index]
- @Operation(group = "sync")
+ @Operation(nonParallelGroup = "writer")
fun set(@Param(name = "index") index: Int, @Param(name = "value") value: Int) {
a.setSynchronized(index, value)
}
-
- override fun extractState() = (0..4).map { a[it] }
}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt
index 09dee56c51..d093e8066a 100644
--- a/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt
@@ -25,8 +25,6 @@ abstract class SemaphoreLincheckTestBase(permits: Int) : AbstractLincheckTest()
override fun > O.customize(isStressTest: Boolean): O =
actorsBefore(0)
- override fun extractState() = semaphore.availablePermits
-
override fun ModelCheckingOptions.customize(isStressTest: Boolean) =
checkObstructionFreedom()
}
diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md
index ff9ba9fffa..117c663afc 100644
--- a/kotlinx-coroutines-debug/README.md
+++ b/kotlinx-coroutines-debug/README.md
@@ -61,7 +61,7 @@ stacktraces will be dumped to the console.
### Using as JVM agent
Debug module can also be used as a standalone JVM agent to enable debug probes on the application startup.
-You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.7.0-RC.jar`.
+You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.7.0.jar`.
Additionally, on Linux and Mac OS X you can use `kill -5 $pid` command in order to force your application to print all alive coroutines.
When used as Java agent, `"kotlinx.coroutines.debug.enable.creation.stack.trace"` system property can be used to control
[DebugProbes.enableCreationStackTraces] along with agent startup.
diff --git a/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt
index 965f17883f..89dd914879 100644
--- a/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt
+++ b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt
@@ -1,7 +1,6 @@
/*
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package kotlinx.coroutines.debug
import kotlinx.coroutines.*
@@ -170,7 +169,8 @@ class RunningThreadStackMergeTest : DebugTestBase() {
assertTrue(true)
}
- @Test
+ @Test // IDEA-specific debugger API test
+ @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
fun testActiveThread() = runBlocking {
launchCoroutine()
awaitCoroutineStarted()
diff --git a/kotlinx-coroutines-debug/test/StacktraceUtils.kt b/kotlinx-coroutines-debug/test/StacktraceUtils.kt
index 55bdd7e0b0..869e29751c 100644
--- a/kotlinx-coroutines-debug/test/StacktraceUtils.kt
+++ b/kotlinx-coroutines-debug/test/StacktraceUtils.kt
@@ -74,6 +74,18 @@ private fun cleanBlockHoundTraces(frames: List): List {
return result
}
+/**
+ * Removes all frames that contain "java.util.concurrent" in it.
+ *
+ * We do leverage Java's locks for proper rendezvous and to fix the coroutine stack's state,
+ * but this API doesn't have (nor expected to) stable stacktrace, so we are filtering all such
+ * frames out.
+ *
+ * See https://github.com/Kotlin/kotlinx.coroutines/issues/3700 for the example of failure
+ */
+private fun removeJavaUtilConcurrentTraces(frames: List): List =
+ frames.filter { !it.contains("java.util.concurrent") }
+
private data class CoroutineDump(
val header: CoroutineDumpHeader,
val coroutineStackTrace: List,
@@ -185,7 +197,9 @@ public fun verifyDump(vararg expectedTraces: String, ignoredCoroutine: String? =
.drop(1)
// Parse dumps and filter out ignored coroutines
.mapNotNull { trace ->
- val dump = CoroutineDump.parse(trace, traceCleaner = ::cleanBlockHoundTraces)
+ val dump = CoroutineDump.parse(trace, {
+ removeJavaUtilConcurrentTraces(cleanBlockHoundTraces(it))
+ })
if (dump.header.className == ignoredCoroutine) {
null
} else {
@@ -194,9 +208,10 @@ public fun verifyDump(vararg expectedTraces: String, ignoredCoroutine: String? =
}
assertEquals(expectedTraces.size, dumps.size)
- dumps.zip(expectedTraces.map(CoroutineDump::parse)).forEach { (dump, expectedDump) ->
- dump.verify(expectedDump)
- }
+ dumps.zip(expectedTraces.map { CoroutineDump.parse(it, ::removeJavaUtilConcurrentTraces) })
+ .forEach { (dump, expectedDump) ->
+ dump.verify(expectedDump)
+ }
}
public fun String.trimPackage() = replace("kotlinx.coroutines.debug.", "")
diff --git a/kotlinx-coroutines-test/README.md b/kotlinx-coroutines-test/README.md
index f2805086bb..fae47fa777 100644
--- a/kotlinx-coroutines-test/README.md
+++ b/kotlinx-coroutines-test/README.md
@@ -26,7 +26,7 @@ Provided [TestDispatcher] implementations:
Add `kotlinx-coroutines-test` to your project test dependencies:
```
dependencies {
- testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.0-RC'
+ testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.0'
}
```
diff --git a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api
index 00d9fb659e..a41502b2e1 100644
--- a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api
+++ b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api
@@ -121,9 +121,11 @@ public final class kotlinx/coroutines/test/TestScopeKt {
public static final fun advanceTimeBy (Lkotlinx/coroutines/test/TestScope;J)V
public static final fun advanceTimeBy-HG0u8IE (Lkotlinx/coroutines/test/TestScope;J)V
public static final fun advanceUntilIdle (Lkotlinx/coroutines/test/TestScope;)V
+ public static final fun getCatchNonTestRelatedExceptions ()Z
public static final fun getCurrentTime (Lkotlinx/coroutines/test/TestScope;)J
public static final fun getTestTimeSource (Lkotlinx/coroutines/test/TestScope;)Lkotlin/time/TimeSource$WithComparableMarks;
public static final fun runCurrent (Lkotlinx/coroutines/test/TestScope;)V
+ public static final fun setCatchNonTestRelatedExceptions (Z)V
}
public abstract interface class kotlinx/coroutines/test/UncaughtExceptionCaptor {
diff --git a/kotlinx-coroutines-test/common/src/TestScope.kt b/kotlinx-coroutines-test/common/src/TestScope.kt
index a5a36a8524..fa6e3930d8 100644
--- a/kotlinx-coroutines-test/common/src/TestScope.kt
+++ b/kotlinx-coroutines-test/common/src/TestScope.kt
@@ -233,7 +233,9 @@ internal class TestScopeImpl(context: CoroutineContext) :
* after the previous one, and learning about such exceptions as soon is possible is nice. */
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
run { ensurePlatformExceptionHandlerLoaded(ExceptionCollector) }
- ExceptionCollector.addOnExceptionCallback(lock, this::reportException)
+ if (catchNonTestRelatedExceptions) {
+ ExceptionCollector.addOnExceptionCallback(lock, this::reportException)
+ }
uncaughtExceptions
}
if (exceptions.isNotEmpty()) {
@@ -312,7 +314,7 @@ internal fun TestScope.asSpecificImplementation(): TestScopeImpl = when (this) {
}
internal class UncaughtExceptionsBeforeTest : IllegalStateException(
- "There were uncaught exceptions in coroutines launched from TestScope before the test started. Please avoid this," +
+ "There were uncaught exceptions before the test started. Please avoid this," +
" as such exceptions are also reported in a platform-dependent manner so that they are not lost."
)
@@ -321,3 +323,12 @@ internal class UncaughtExceptionsBeforeTest : IllegalStateException(
*/
@ExperimentalCoroutinesApi
internal class UncompletedCoroutinesError(message: String) : AssertionError(message)
+
+/**
+ * A flag that controls whether [TestScope] should attempt to catch arbitrary exceptions flying through the system.
+ * If it is enabled, then any exception that is not caught by the user code will be reported as a test failure.
+ * By default, it is enabled, but some tests may want to disable it to test the behavior of the system when they have
+ * their own exception handling procedures.
+ */
+@PublishedApi
+internal var catchNonTestRelatedExceptions: Boolean = true
diff --git a/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt b/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt
index 90fa763523..70fcb9487f 100644
--- a/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt
+++ b/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt
@@ -43,8 +43,10 @@ internal object ExceptionCollector : AbstractCoroutineContextElement(CoroutineEx
* Unregisters the callback associated with [owner].
*/
fun removeOnExceptionCallback(owner: Any) = synchronized(lock) {
- val existingValue = callbacks.remove(owner)
- check(existingValue !== null)
+ if (enabled) {
+ val existingValue = callbacks.remove(owner)
+ check(existingValue !== null)
+ }
}
/**
diff --git a/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md
index e00d74edee..f45c382a2f 100644
--- a/ui/coroutines-guide-ui.md
+++ b/ui/coroutines-guide-ui.md
@@ -110,7 +110,7 @@ Add dependencies on `kotlinx-coroutines-android` module to the `dependencies { .
`app/build.gradle` file:
```groovy
-implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0-RC"
+implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0"
```
You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your