Skip to content

Commit 2e92d58

Browse files
authored
Merge pull request #3740 from Kotlin/version-1.7.0
Version 1.7.0
2 parents c8ef9ec + 72ef8fd commit 2e92d58

29 files changed

+217
-110
lines changed

.idea/codeStyles/Project.xml

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CHANGES.md

+73
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,78 @@
11
# Change log for kotlinx.coroutines
22

3+
## Version 1.7.0
4+
5+
### Core API significant improvements
6+
7+
* New `Channel` implementation with significant performance improvements across the API (#3621).
8+
* New `select` operator implementation: faster, more lightweight, and more robust (#3020).
9+
* `Mutex` and `Semaphore` now share the same underlying data structure (#3020).
10+
* `Dispatchers.IO` is added to K/N (#3205)
11+
* `newFixedThreadPool` and `Dispatchers.Default` implementations on K/N were wholly rewritten to support graceful growth under load (#3595).
12+
* `kotlinx-coroutines-test` rework:
13+
- 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).
14+
- The `withTimeout` exception messages indicate if the timeout used the virtual time (#3588).
15+
- `TestCoroutineScheduler`, `runTest`, and `TestScope` API are promoted to stable (#3622).
16+
- `runTest` now also fails if there were uncaught exceptions in coroutines not inherited from the test coroutine (#1205).
17+
18+
### Breaking changes
19+
20+
* Old K/N memory model is no longer supported (#3375).
21+
* New generic upper bounds were added to reactive integration API where the language since 1.8.0 dictates (#3393).
22+
* `kotlinx-coroutines-core` and `kotlinx-coroutines-jdk8` artifacts were merged into a single artifact (#3268).
23+
* Artificial stackframes in stacktrace recovery no longer contain the `\b` symbol and are now navigable in IDE and supplied with proper documentation (#2291).
24+
* `CoroutineContext.isActive` returns `true` for contexts without any job in them (#3300).
25+
26+
### Bug fixes and improvements
27+
28+
* Kotlin version is updated to 1.8.20
29+
* Atomicfu version is updated to 0.20.2.
30+
* `JavaFx` version is updated to 17.0.2 in `kotlinx-coroutines-javafx` (#3671)..
31+
* JPMS is supported (#2237). Thanks @lion7!
32+
* `BroadcastChannel` and all the corresponding API are deprecated (#2680).
33+
* Added all supported K/N targets (#3601, #812, #855).
34+
* K/N `Dispatchers.Default` is backed by the number of threads equal to the number of available cores (#3366).
35+
* Fixed an issue where some coroutines' internal exceptions were not properly serializable (#3328).
36+
* Introduced `Job.parent` API (#3201).
37+
* Fixed a bug when `TestScheduler` leaked cancelled jobs (#3398).
38+
* `TestScope.timeSource` now provides comparable time marks (#3617). Thanks @hfhbd!
39+
* Fixed an issue when cancelled `withTimeout` handles were preserved in JS runtime (#3440).
40+
* Ensure `awaitFrame` only awaits a single frame when used from the main looper (#3432). Thanks @pablobaxter!
41+
* Obsolete `Class-Path` attribute was removed from `kotlinx-coroutines-debug.jar` manifest (#3361).
42+
* Fixed a bug when `updateThreadContext` operated on the parent context (#3411).
43+
* Added new `Flow.filterIsInstance` extension (#3240).
44+
* `Dispatchers.Default` thread name prefixes are now configurable with system property (#3231).
45+
* Added `Flow.timeout` operator as `@FlowPreview` (#2624). Thanks @pablobaxter!
46+
* Improved the performance of the `future` builder in case of exceptions (#3475). Thanks @He-Pin!
47+
* `Mono.awaitSingleOrNull` now waits for the `onComplete` signal (#3487).
48+
* `Channel.isClosedForSend` and `Channel.isClosedForReceive` are promoted from experimental to delicate (#3448).
49+
* Fixed a data race in native `EventLoop` (#3547).
50+
* `Dispatchers.IO.limitedParallelism(valueLargerThanIOSize)` no longer creates an additional wrapper (#3442). Thanks @dovchinnikov!
51+
* Various `@FlowPreview` and `@ExperimentalCoroutinesApi` are promoted to experimental and stable respectively (#3542, #3097, #3548).
52+
* Performance improvements in `Dispatchers.Default` and `Dispatchers.IO` (#3416, #3418).
53+
* Fixed a bug when internal `suspendCancellableCoroutineReusable` might have hanged (#3613).
54+
* Introduced internal API to process events in the current system dispatcher (#3439).
55+
* Global `CoroutineExceptionHandler` is no longer invoked in case of unprocessed `future` failure (#3452).
56+
* Performance improvements and reduced thread-local pressure for the `withContext` operator (#3592).
57+
* Improved performance of `DebugProbes` (#3527).
58+
* Fixed a bug when the coroutine debugger might have detected the state of a coroutine incorrectly (#3193).
59+
* `CoroutineDispatcher.asExecutor()` runs tasks without dispatching if the dispatcher is unconfined (#3683). Thanks @odedniv!
60+
* `SharedFlow.toMutableList` and `SharedFlow.toSet` lints are introduced (#3706).
61+
* `Channel.invokeOnClose` is promoted to stable API (#3358).
62+
* Improved lock contention in `Dispatchers.Default` and `Dispatchers.IO` during the startup phase (#3652).
63+
* Fixed a bug that led to threads oversubscription in `Dispatchers.Default` (#3642).
64+
* Fixed a bug that allowed `limitedParallelism` to perform dispatches even after the underlying dispatcher was closed (#3672).
65+
* Fixed a bug that prevented stacktrace recovery when the exception's constructor from `cause` was selected (#3714).
66+
* Improved sanitizing of stracktrace-recovered traces (#3714).
67+
* Introduced an internal flag to disable uncaught exceptions reporting in tests as a temporary migration mechanism (#3736).
68+
* Various documentation improvements and fixes.
69+
70+
### Changelog relative to version 1.7.0-RC
71+
72+
* Fixed a bug that prevented stacktrace recovery when the exception's constructor from `cause` was selected (#3714).
73+
* Improved sanitizing of stracktrace-recovered traces (#3714).
74+
* Introduced an internal flag to disable uncaught exceptions reporting in tests as a temporary migration mechanism (#3736).
75+
376
## Version 1.7.0-RC
477

578
* Kotlin version is updated to 1.8.20.

README.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Kotlin Stable](https://kotl.in/badges/stable.svg)](https://kotlinlang.org/docs/components-stability.html)
44
[![JetBrains official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
55
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0)
6-
[![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)
6+
[![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)
77
[![Kotlin](https://img.shields.io/badge/kotlin-1.8.20-blue.svg?logo=kotlin)](http://kotlinlang.org)
88
[![Slack channel](https://img.shields.io/badge/chat-slack-green.svg?logo=slack)](https://kotlinlang.slack.com/messages/coroutines/)
99

@@ -85,7 +85,7 @@ Add dependencies (you can also add other modules that you need):
8585
<dependency>
8686
<groupId>org.jetbrains.kotlinx</groupId>
8787
<artifactId>kotlinx-coroutines-core</artifactId>
88-
<version>1.7.0-RC</version>
88+
<version>1.7.0</version>
8989
</dependency>
9090
```
9191

@@ -103,7 +103,7 @@ Add dependencies (you can also add other modules that you need):
103103

104104
```kotlin
105105
dependencies {
106-
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-RC")
106+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0")
107107
}
108108
```
109109

@@ -133,7 +133,7 @@ Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android)
133133
module as a dependency when using `kotlinx.coroutines` on Android:
134134

135135
```kotlin
136-
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0-RC")
136+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0")
137137
```
138138

139139
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
168168
```kotlin
169169
commonMain {
170170
dependencies {
171-
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-RC")
171+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0")
172172
}
173173
}
174174
```
@@ -180,7 +180,7 @@ Platform-specific dependencies are recommended to be used only for non-multiplat
180180
#### JS
181181

182182
Kotlin/JS version of `kotlinx.coroutines` is published as
183-
[`kotlinx-coroutines-core-js`](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.7.0-RC)
183+
[`kotlinx-coroutines-core-js`](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.7.0)
184184
(follow the link to get the dependency declaration snippet) and as [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotlinx-coroutines-core) NPM package.
185185

186186
#### Native

benchmarks/build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ java {
2424
tasks.named<KotlinCompile>("compileJmhKotlin") {
2525
kotlinOptions {
2626
jvmTarget = "1.8"
27-
freeCompilerArgs += "-Xjvm-default=enable"
27+
freeCompilerArgs += "-Xjvm-default=all"
2828
}
2929
}
3030

benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt

-2
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,12 @@ abstract class ShakespearePlaysScrabble {
3434
public interface LongWrapper {
3535
fun get(): Long
3636

37-
@JvmDefault
3837
fun incAndSet(): LongWrapper {
3938
return object : LongWrapper {
4039
override fun get(): Long = this@LongWrapper.get() + 1L
4140
}
4241
}
4342

44-
@JvmDefault
4543
fun add(other: LongWrapper): LongWrapper {
4644
return object : LongWrapper {
4745
override fun get(): Long = this@LongWrapper.get() + other.get()

gradle.properties

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44

55
# Kotlin
6-
version=1.7.0-RC-SNAPSHOT
6+
version=1.7.0-SNAPSHOT
77
group=org.jetbrains.kotlinx
88
kotlin_version=1.8.20
99

@@ -13,7 +13,7 @@ junit5_version=5.7.0
1313
atomicfu_version=0.20.2
1414
knit_version=0.4.0
1515
html_version=0.7.2
16-
lincheck_version=2.16
16+
lincheck_version=2.17
1717
dokka_version=1.8.10
1818
byte_buddy_version=1.10.9
1919
reactor_version=3.4.1

integration-testing/gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
kotlin_version=1.8.20
2-
coroutines_version=1.7.0-RC-SNAPSHOT
2+
coroutines_version=1.7.0-SNAPSHOT
33
asm_version=9.3
44

55
kotlin.code.style=official

kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1220,9 +1220,9 @@ internal open class BufferedChannel<E>(
12201220
incCompletedExpandBufferAttempts()
12211221
return
12221222
}
1223-
// Is `bufferEndSegment` outdated?
1223+
// Is `bufferEndSegment` outdated or is the segment with the required id already removed?
12241224
// Find the required segment, creating new ones if needed.
1225-
if (segment.id < id) {
1225+
if (segment.id != id) {
12261226
segment = findSegmentBufferEnd(id, segment, b)
12271227
// Restart if the required segment is removed, or
12281228
// the linked list of segments is already closed,

kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstructor.kt

+37-31
Original file line numberDiff line numberDiff line change
@@ -32,42 +32,48 @@ internal fun <E : Throwable> tryCopyException(exception: E): E? {
3232

3333
private fun <E : Throwable> createConstructor(clz: Class<E>): Ctor {
3434
val nullResult: Ctor = { null } // Pre-cache class
35-
// Skip reflective copy if an exception has additional fields (that are usually populated in user-defined constructors)
35+
// Skip reflective copy if an exception has additional fields (that are typically populated in user-defined constructors)
3636
if (throwableFields != clz.fieldsCountOrDefault(0)) return nullResult
3737
/*
38-
* Try to reflectively find constructor(), constructor(message, cause), constructor(cause) or constructor(message).
39-
* Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
40-
*/
41-
val constructors = clz.constructors.sortedByDescending { it.parameterTypes.size }
42-
for (constructor in constructors) {
43-
val result = createSafeConstructor(constructor)
44-
if (result != null) return result
45-
}
46-
return nullResult
47-
}
48-
49-
private fun createSafeConstructor(constructor: Constructor<*>): Ctor? {
50-
val p = constructor.parameterTypes
51-
return when (p.size) {
52-
2 -> when {
53-
p[0] == String::class.java && p[1] == Throwable::class.java ->
54-
safeCtor { e -> constructor.newInstance(e.message, e) as Throwable }
55-
else -> null
38+
* Try to reflectively find constructor(message, cause), constructor(message), constructor(cause), or constructor(),
39+
* in that order of priority.
40+
* Exceptions are shared among coroutines, so we should copy exception before recovering current stacktrace.
41+
*
42+
* By default, Java's reflection iterates over ctors in the source-code order and the sorting is stable, so we can
43+
* not rely on the order of iteration. Instead, we assign a unique priority to each ctor type.
44+
*/
45+
return clz.constructors.map { constructor ->
46+
val p = constructor.parameterTypes
47+
when (p.size) {
48+
2 -> when {
49+
p[0] == String::class.java && p[1] == Throwable::class.java ->
50+
safeCtor { e -> constructor.newInstance(e.message, e) as Throwable } to 3
51+
else -> null to -1
52+
}
53+
1 -> when (p[0]) {
54+
String::class.java ->
55+
safeCtor { e -> (constructor.newInstance(e.message) as Throwable).also { it.initCause(e) } } to 2
56+
Throwable::class.java ->
57+
safeCtor { e -> constructor.newInstance(e) as Throwable } to 1
58+
else -> null to -1
59+
}
60+
0 -> safeCtor { e -> (constructor.newInstance() as Throwable).also { it.initCause(e) } } to 0
61+
else -> null to -1
5662
}
57-
1 -> when (p[0]) {
58-
Throwable::class.java ->
59-
safeCtor { e -> constructor.newInstance(e) as Throwable }
60-
String::class.java ->
61-
safeCtor { e -> (constructor.newInstance(e.message) as Throwable).also { it.initCause(e) } }
62-
else -> null
63-
}
64-
0 -> safeCtor { e -> (constructor.newInstance() as Throwable).also { it.initCause(e) } }
65-
else -> null
66-
}
63+
}.maxByOrNull(Pair<*, Int>::second)?.first ?: nullResult
6764
}
6865

69-
private inline fun safeCtor(crossinline block: (Throwable) -> Throwable): Ctor =
70-
{ e -> runCatching { block(e) }.getOrNull() }
66+
private fun safeCtor(block: (Throwable) -> Throwable): Ctor = { e ->
67+
runCatching {
68+
val result = block(e)
69+
/*
70+
* Verify that the new exception has the same message as the original one (bail out if not, see #1631)
71+
* or if the new message complies the contract from `Throwable(cause).message` contract.
72+
*/
73+
if (e.message != result.message && result.message != e.toString()) null
74+
else result
75+
}.getOrNull()
76+
}
7177

7278
private fun Class<*>.fieldsCountOrDefault(defaultValue: Int) =
7379
kotlin.runCatching { fieldsCount() }.getOrDefault(defaultValue)

kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt

+6-15
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,16 @@ private val stackTraceRecoveryClassName = runCatching {
3333
internal actual fun <E : Throwable> recoverStackTrace(exception: E): E {
3434
if (!RECOVER_STACK_TRACES) return exception
3535
// No unwrapping on continuation-less path: exception is not reported multiple times via slow paths
36-
val copy = tryCopyAndVerify(exception) ?: return exception
36+
val copy = tryCopyException(exception) ?: return exception
3737
return copy.sanitizeStackTrace()
3838
}
3939

4040
private fun <E : Throwable> E.sanitizeStackTrace(): E {
4141
val stackTrace = stackTrace
4242
val size = stackTrace.size
43-
val lastIntrinsic = stackTrace.frameIndex(stackTraceRecoveryClassName)
43+
val lastIntrinsic = stackTrace.indexOfLast { stackTraceRecoveryClassName == it.className }
4444
val startIndex = lastIntrinsic + 1
45-
val endIndex = stackTrace.frameIndex(baseContinuationImplClassName)
45+
val endIndex = stackTrace.firstFrameIndex(baseContinuationImplClassName)
4646
val adjustment = if (endIndex == -1) 0 else size - endIndex
4747
val trace = Array(size - lastIntrinsic - adjustment) {
4848
if (it == 0) {
@@ -70,7 +70,7 @@ private fun <E : Throwable> recoverFromStackFrame(exception: E, continuation: Co
7070
val (cause, recoveredStacktrace) = exception.causeAndStacktrace()
7171

7272
// Try to create an exception of the same type and get stacktrace from continuation
73-
val newException = tryCopyAndVerify(cause) ?: return exception
73+
val newException = tryCopyException(cause) ?: return exception
7474
// Update stacktrace
7575
val stacktrace = createStackTrace(continuation)
7676
if (stacktrace.isEmpty()) return exception
@@ -82,14 +82,6 @@ private fun <E : Throwable> recoverFromStackFrame(exception: E, continuation: Co
8282
return createFinalException(cause, newException, stacktrace)
8383
}
8484

85-
private fun <E : Throwable> tryCopyAndVerify(exception: E): E? {
86-
val newException = tryCopyException(exception) ?: return null
87-
// Verify that the new exception has the same message as the original one (bail out if not, see #1631)
88-
// CopyableThrowable has control over its message and thus can modify it the way it wants
89-
if (exception !is CopyableThrowable<*> && newException.message != exception.message) return null
90-
return newException
91-
}
92-
9385
/*
9486
* Here we partially copy original exception stackTrace to make current one much prettier.
9587
* E.g. for
@@ -109,7 +101,7 @@ private fun <E : Throwable> tryCopyAndVerify(exception: E): E? {
109101
private fun <E : Throwable> createFinalException(cause: E, result: E, resultStackTrace: ArrayDeque<StackTraceElement>): E {
110102
resultStackTrace.addFirst(ARTIFICIAL_FRAME)
111103
val causeTrace = cause.stackTrace
112-
val size = causeTrace.frameIndex(baseContinuationImplClassName)
104+
val size = causeTrace.firstFrameIndex(baseContinuationImplClassName)
113105
if (size == -1) {
114106
result.stackTrace = resultStackTrace.toTypedArray()
115107
return result
@@ -157,7 +149,6 @@ private fun mergeRecoveredTraces(recoveredStacktrace: Array<StackTraceElement>,
157149
}
158150
}
159151

160-
@Suppress("NOTHING_TO_INLINE")
161152
internal actual suspend inline fun recoverAndThrow(exception: Throwable): Nothing {
162153
if (!RECOVER_STACK_TRACES) throw exception
163154
suspendCoroutineUninterceptedOrReturn<Nothing> {
@@ -198,7 +189,7 @@ private fun createStackTrace(continuation: CoroutineStackFrame): ArrayDeque<Stac
198189
}
199190

200191
internal fun StackTraceElement.isArtificial() = className.startsWith(ARTIFICIAL_FRAME_PACKAGE_NAME)
201-
private fun Array<StackTraceElement>.frameIndex(methodName: String) = indexOfFirst { methodName == it.className }
192+
private fun Array<StackTraceElement>.firstFrameIndex(methodName: String) = indexOfFirst { methodName == it.className }
202193

203194
private fun StackTraceElement.elementWiseEquals(e: StackTraceElement): Boolean {
204195
/*
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
kotlinx.coroutines.RecoverableTestException
2-
at kotlinx.coroutines.internal.StackTraceRecoveryKt.recoverStackTrace(StackTraceRecovery.kt)
32
at kotlinx.coroutines.channels.BufferedChannel.receive$suspendImpl(BufferedChannel.kt)
43
at kotlinx.coroutines.channels.BufferedChannel.receive(BufferedChannel.kt)
54
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelReceive(StackTraceRecoveryChannelsTest.kt)
65
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.access$channelReceive(StackTraceRecoveryChannelsTest.kt)
76
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$channelReceive$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt)
87
Caused by: kotlinx.coroutines.RecoverableTestException
98
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt)
10-
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)
9+
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)

0 commit comments

Comments
 (0)