From bc35cd8f80af1c7961291c15ac988d53a711e4ba Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 26 Jul 2023 17:31:22 +0200 Subject: [PATCH 01/53] Update Kotlin to 1.9.0 (#3814) * Update Kotlin to 1.9.0 * Get rid of legacy JS post 1.9.0 * Stop publishing into NPM * Stop testing on exotic JS targets * Update DebugProbesKt.bin Fixes #3812 --- README.md | 10 +- build.gradle | 4 +- ...nfigure-compilation-conventions.gradle.kts | 2 +- .../kotlin/kotlin-js-conventions.gradle.kts | 3 +- gradle.properties | 14 +-- gradle/compile-js-multiplatform.gradle | 67 +---------- gradle/node-js.gradle | 40 ------- gradle/publish-npm-js.gradle | 54 --------- gradle/test-mocha-js.gradle | 105 ------------------ integration-testing/gradle.properties | 2 +- js/example-frontend-js/build.gradle.kts | 16 +-- .../api/kotlinx-coroutines-core.api | 4 + kotlinx-coroutines-core/build.gradle | 1 - .../jvm/resources/DebugProbesKt.bin | Bin 1738 -> 1738 bytes .../api/kotlinx-coroutines-debug.api | 1 + 15 files changed, 27 insertions(+), 296 deletions(-) delete mode 100644 gradle/node-js.gradle delete mode 100644 gradle/publish-npm-js.gradle delete mode 100644 gradle/test-mocha-js.gradle diff --git a/README.md b/README.md index d82b78639a..e675caef6e 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ [![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.2)](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.2) -[![Kotlin](https://img.shields.io/badge/kotlin-1.8.20-blue.svg?logo=kotlin)](http://kotlinlang.org) +[![Kotlin](https://img.shields.io/badge/kotlin-1.9.0-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/) Library support for Kotlin coroutines with [multiplatform](#multiplatform) support. -This is a companion version for the Kotlin `1.8.20` release. +This is a companion version for the Kotlin `1.9.0` release. ```kotlin suspend fun main() = coroutineScope { @@ -93,7 +93,7 @@ And make sure that you use the latest Kotlin version: ```xml - 1.8.20 + 1.9.0 ``` @@ -112,10 +112,10 @@ And make sure that you use the latest Kotlin version: ```kotlin plugins { // For build.gradle.kts (Kotlin DSL) - kotlin("jvm") version "1.8.20" + kotlin("jvm") version "1.9.0" // For build.gradle (Groovy DSL) - id "org.jetbrains.kotlin.jvm" version "1.8.20" + id "org.jetbrains.kotlin.jvm" version "1.9.0" } ``` diff --git a/build.gradle b/build.gradle index e7d405e124..0e7ee1d471 100644 --- a/build.gradle +++ b/build.gradle @@ -63,7 +63,6 @@ buildscript { classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version" classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version" classpath "org.jetbrains.kotlinx:kotlinx-knit:$knit_version" - classpath "com.github.node-gradle:gradle-node-plugin:$gradle_node_version" classpath "org.jetbrains.kotlinx:binary-compatibility-validator:$binary_compatibility_validator_version" classpath "ru.vyarus:gradle-animalsniffer-plugin:1.5.4" // Android API check classpath "org.jetbrains.kotlin:atomicfu:$kotlin_version" @@ -161,7 +160,6 @@ configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != core } apply from: rootProject.file("gradle/compile-js-multiplatform.gradle") - apply from: rootProject.file("gradle/publish-npm-js.gradle") kotlin.sourceSets.commonMain.dependencies { api project(":$coreModule") } @@ -303,7 +301,7 @@ allprojects { // --------------- Configure sub-projects that are published --------------- -def publishTasks = getTasksByName("publish", true) + getTasksByName("publishNpm", true) +def publishTasks = getTasksByName("publish", true) task deploy(dependsOn: publishTasks) diff --git a/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts b/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts index 9e22b4515c..a891cf0ec2 100644 --- a/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts @@ -28,4 +28,4 @@ configure(subprojects) { } val KotlinCommonOptions.versionsAreNotOverridden: Boolean - get() = languageVersion == null && apiVersion == null \ No newline at end of file + get() = languageVersion == null && apiVersion == null diff --git a/buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts b/buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts index c1897ca749..f8c7ccab8d 100644 --- a/buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts @@ -5,6 +5,7 @@ // Platform-specific configuration to compile JS modules import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile +import org.jetbrains.kotlin.gradle.targets.js.* plugins { kotlin("js") @@ -15,7 +16,7 @@ dependencies { } kotlin { - js(LEGACY) { + js(IR) { moduleName = project.name.removeSuffix("-js") } diff --git a/gradle.properties b/gradle.properties index d558080072..914291afcf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ # Kotlin version=1.7.2-SNAPSHOT group=org.jetbrains.kotlinx -kotlin_version=1.8.20 +kotlin_version=1.9.0 # Dependencies junit_version=4.12 @@ -33,18 +33,6 @@ androidx_annotation_version=1.1.0 robolectric_version=4.9 baksmali_version=2.2.7 -# JS -kotlin.js.compiler=both -gradle_node_version=3.1.1 -node_version=10.0.0 -npm_version=5.7.1 -mocha_version=6.2.2 -mocha_headless_chrome_version=1.8.2 -mocha_teamcity_reporter_version=3.0.0 -source_map_support_version=0.5.16 -jsdom_version=15.2.1 -jsdom_global_version=3.0.2 - # Settings kotlin.incremental.multiplatform=true kotlin.native.ignoreDisabledTargets=true diff --git a/gradle/compile-js-multiplatform.gradle b/gradle/compile-js-multiplatform.gradle index c6fc757c7d..4200972cea 100644 --- a/gradle/compile-js-multiplatform.gradle +++ b/gradle/compile-js-multiplatform.gradle @@ -2,21 +2,12 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -apply from: rootProject.file('gradle/node-js.gradle') - kotlin { js { moduleName = project.name - - // In 1.4.x it has in `both` and `legacy` mode and js() is of type `KotlinJsTarget` - // `irTarget` is non-null in `both` mode - // and contains appropriate `irTarget` with type `KotlinJsIrTarget` - // `irTarget` is null in `legacy` mode - if (it.irTarget != null) { - irTarget.nodejs() - irTarget.compilations['main']?.dependencies { - api "org.jetbrains.kotlinx:atomicfu-js:$atomicfu_version" - } + nodejs() + compilations['main']?.dependencies { + api "org.jetbrains.kotlinx:atomicfu-js:$atomicfu_version" } } @@ -26,55 +17,3 @@ kotlin { } } } - -// When source sets are configured -apply from: rootProject.file('gradle/test-mocha-js.gradle') - -def compileJsLegacy = tasks.hasProperty("compileKotlinJsLegacy") - ? compileKotlinJsLegacy - : compileKotlinJs - -def compileTestJsLegacy = tasks.hasProperty("compileTestKotlinJsLegacy") - ? compileTestKotlinJsLegacy - : compileTestKotlinJs - -compileJsLegacy.configure { - kotlinOptions.metaInfo = true - kotlinOptions.sourceMap = true - kotlinOptions.moduleKind = 'umd' - - kotlinOptions { - // drop -js suffix from outputFile - def baseName = project.name - "-js" - outputFile = new File(outputFileProperty.get().parent, baseName + ".js") - } -} - -compileTestJsLegacy.configure { - kotlinOptions.metaInfo = true - kotlinOptions.sourceMap = true - kotlinOptions.moduleKind = 'umd' -} - - -task populateNodeModules(type: Copy, dependsOn: compileTestJsLegacy) { - // we must copy output that is transformed by atomicfu - from(kotlin.js().compilations.main.output.allOutputs) - into node.nodeProjectDir.dir("node_modules") - - def configuration = configurations.hasProperty("jsLegacyTestRuntimeClasspath") - ? configurations.jsLegacyTestRuntimeClasspath - : configurations.jsTestRuntimeClasspath - - from(files { - configuration.collect { File file -> - file.name.endsWith(".jar") ? - zipTree(file.absolutePath).matching { - include '*.js' - include '*.js.map' - } : files() - } - }.builtBy(configuration)) -} - -npmInstall.dependsOn populateNodeModules diff --git a/gradle/node-js.gradle b/gradle/node-js.gradle deleted file mode 100644 index 5eddc5fa37..0000000000 --- a/gradle/node-js.gradle +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -apply plugin: 'com.github.node-gradle.node' - -node { - version = "$node_version" - npmVersion = "$npm_version" - download = true - nodeProjectDir = file(buildDir) -} - -// Configures testing for JS modules - -task prepareNodePackage(type: Copy) { - from("npm") { - include 'package.json' - // Postpone expansion of package.json until we configure version property in build.gradle - def copySpec = it - afterEvaluate { - copySpec.expand(project.properties + [kotlinDependency: ""]) - } - } - from("npm") { - exclude 'package.json' - } - into node.nodeProjectDir -} - -npmInstall.dependsOn prepareNodePackage - -// Workaround the problem with Node downloading -repositories.whenObjectAdded { - if (it instanceof IvyArtifactRepository) { - metadataSources { - artifact() - } - } -} diff --git a/gradle/publish-npm-js.gradle b/gradle/publish-npm-js.gradle deleted file mode 100644 index 9d4152770c..0000000000 --- a/gradle/publish-npm-js.gradle +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -def prop(name, defVal) { - def value = project.properties[name] - if (value == null) return defVal - return value -} - -def distTag(version) { - def i = version.indexOf('-') - if (i > 0) return version.substring(i + 1) - return "latest" -} - -def npmTemplateDir = file("$projectDir/npm") -def npmDeployDir = file("$buildDir/npm") - -def authToken = prop("kotlin.npmjs.auth.token", "") -def dryRun = prop("dryRun", "false") - -def jsLegacy = kotlin.targets.hasProperty("jsLegacy") - ? kotlin.targets.jsLegacy - : kotlin.targets.js - -// Note: publish transformed files using dependency on sourceSets.main.output -task preparePublishNpm(type: Copy) { - from(npmTemplateDir) { - // Postpone expansion of package.json until we configure version property in build.gradle - def copySpec = it - afterEvaluate { - copySpec.expand(project.properties + [kotlinDependency: "\"kotlin\": \"$kotlin_version\""]) - } - } - // we must publish output that is transformed by atomicfu - from(jsLegacy.compilations.main.output.allOutputs) - into npmDeployDir -} - -task publishNpm(type: NpmTask, dependsOn: [preparePublishNpm]) { - workingDir = npmDeployDir - - def npmDeployTag = distTag(version) - def deployArgs = ['publish', - "--//registry.npmjs.org/:_authToken=$authToken", - "--tag=$npmDeployTag"] - if (dryRun == "true") { - println("$npmDeployDir \$ npm arguments: $deployArgs") - args = ['pack'] - } else { - args = deployArgs - } -} diff --git a/gradle/test-mocha-js.gradle b/gradle/test-mocha-js.gradle deleted file mode 100644 index 1ec297e415..0000000000 --- a/gradle/test-mocha-js.gradle +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -// -- Testing with Mocha under Node - -task installDependenciesMochaNode(type: NpmTask, dependsOn: [npmInstall]) { - args = ['install', - "mocha@$mocha_version", - "source-map-support@$source_map_support_version", - '--no-save'] - if (project.hasProperty("teamcity")) args.addAll(["mocha-teamcity-reporter@$mocha_teamcity_reporter_version"]) -} - -def compileJsLegacy = tasks.hasProperty("compileKotlinJsLegacy") - ? compileKotlinJsLegacy - : compileKotlinJs - -def compileTestJsLegacy = tasks.hasProperty("compileTestKotlinJsLegacy") - ? compileTestKotlinJsLegacy - : compileTestKotlinJs - -// todo: use atomicfu-transformed test files here (not critical) -task testMochaNode(type: NodeTask, dependsOn: [compileTestJsLegacy, installDependenciesMochaNode]) { - script = file("${node.nodeProjectDir.getAsFile().get()}/node_modules/mocha/bin/mocha") - args = [compileTestJsLegacy.outputFileProperty.get().path, '--require', 'source-map-support/register'] - if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter']) -} - -def jsLegacyTestTask = project.tasks.findByName('jsLegacyTest') ? jsLegacyTest : jsTest - -// TODO -//jsLegacyTestTask.dependsOn testMochaNode - -// -- Testing with Mocha under headless Chrome - -task installDependenciesMochaChrome(type: NpmTask, dependsOn: [npmInstall]) { - args = ['install', - "mocha@$mocha_version", - "mocha-headless-chrome@$mocha_headless_chrome_version", - "kotlin@$kotlin_version", - "kotlin-test@$kotlin_version", - '--no-save'] - if (project.hasProperty("teamcity")) args.addAll([ - "mocha-teamcity-reporter@$mocha_teamcity_reporter_version"]) -} - -def mochaChromeTestPage = file("$buildDir/test-page.html") - -task prepareMochaChrome(dependsOn: [compileTestJsLegacy, installDependenciesMochaChrome]) { - outputs.file(mochaChromeTestPage) -} - -prepareMochaChrome.doLast { - def nodeProjDir = node.nodeProjectDir.getAsFile().get() - mochaChromeTestPage.text = """ - - - Mocha Tests - - - - -
- - - - - - - - - - """ -} - -task testMochaChrome(type: NodeTask, dependsOn: prepareMochaChrome) { - script = file("${node.nodeProjectDir.getAsFile().get()}/node_modules/mocha-headless-chrome/bin/start") - args = [compileTestJsLegacy.outputFileProperty.get().path, '--file', mochaChromeTestPage] - if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter']) -} - -// todo: Commented out because mocha-headless-chrome does not work on TeamCity -//jsTest.dependsOn testMochaChrome - -// -- Testing with Mocha under jsdom - -task installDependenciesMochaJsdom(type: NpmTask, dependsOn: [npmInstall]) { - args = ['install', - "mocha@$mocha_version", - "jsdom@$jsdom_version", - "jsdom-global@$jsdom_global_version", - "source-map-support@$source_map_support_version", - '--no-save'] - if (project.hasProperty("teamcity")) args.addAll(["mocha-teamcity-reporter@$mocha_teamcity_reporter_version"]) -} - -task testMochaJsdom(type: NodeTask, dependsOn: [compileTestJsLegacy, installDependenciesMochaJsdom]) { - script = file("${node.nodeProjectDir.getAsFile().get()}/node_modules/mocha/bin/mocha") - args = [compileTestJsLegacy.outputFileProperty.get().path, '--require', 'source-map-support/register', '--require', 'jsdom-global/register'] - if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter']) -} - -// TODO -//jsLegacyTestTask.dependsOn testMochaJsdom diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties index 30b2b5ed62..59811645d7 100644 --- a/integration-testing/gradle.properties +++ b/integration-testing/gradle.properties @@ -1,4 +1,4 @@ -kotlin_version=1.8.20 +kotlin_version=1.9.0 coroutines_version=1.7.2-SNAPSHOT asm_version=9.3 diff --git a/js/example-frontend-js/build.gradle.kts b/js/example-frontend-js/build.gradle.kts index 1cc587b740..ec718a28ad 100644 --- a/js/example-frontend-js/build.gradle.kts +++ b/js/example-frontend-js/build.gradle.kts @@ -3,22 +3,22 @@ */ kotlin { - js(LEGACY) { + js(IR) { binaries.executable() browser { - distribution { - directory = directory.parentFile.resolve("dist") - } - commonWebpackConfig { + distribution(Action { + outputDirectory.set(outputDirectory.get().asFile.parentFile.resolve("dist")) + }) + commonWebpackConfig(Action { cssSupport { enabled.set(true) } - } - testTask { + }) + testTask(Action { useKarma { useChromeHeadless() } - } + }) } } } diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 234bf10420..de53f89a21 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -264,6 +264,7 @@ public final class kotlinx/coroutines/CoroutineStart : java/lang/Enum { public static final field DEFAULT Lkotlinx/coroutines/CoroutineStart; public static final field LAZY Lkotlinx/coroutines/CoroutineStart; public static final field UNDISPATCHED Lkotlinx/coroutines/CoroutineStart; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public final fun invoke (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V public final fun invoke (Lkotlin/jvm/functions/Function2;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)V public final fun isLazy ()Z @@ -672,6 +673,7 @@ public final class kotlinx/coroutines/channels/BufferOverflow : java/lang/Enum { public static final field DROP_LATEST Lkotlinx/coroutines/channels/BufferOverflow; public static final field DROP_OLDEST Lkotlinx/coroutines/channels/BufferOverflow; public static final field SUSPEND Lkotlinx/coroutines/channels/BufferOverflow; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/channels/BufferOverflow; public static fun values ()[Lkotlinx/coroutines/channels/BufferOverflow; } @@ -912,6 +914,7 @@ public final class kotlinx/coroutines/channels/TickerChannelsKt { public final class kotlinx/coroutines/channels/TickerMode : java/lang/Enum { public static final field FIXED_DELAY Lkotlinx/coroutines/channels/TickerMode; public static final field FIXED_PERIOD Lkotlinx/coroutines/channels/TickerMode; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/channels/TickerMode; public static fun values ()[Lkotlinx/coroutines/channels/TickerMode; } @@ -1183,6 +1186,7 @@ public final class kotlinx/coroutines/flow/SharingCommand : java/lang/Enum { public static final field START Lkotlinx/coroutines/flow/SharingCommand; public static final field STOP Lkotlinx/coroutines/flow/SharingCommand; public static final field STOP_AND_RESET_REPLAY_CACHE Lkotlinx/coroutines/flow/SharingCommand; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/flow/SharingCommand; public static fun values ()[Lkotlinx/coroutines/flow/SharingCommand; } diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index 6b0d6c2c09..7724d190af 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -16,7 +16,6 @@ if (rootProject.ext.native_targets_enabled) { } apply from: rootProject.file("gradle/compile-js-multiplatform.gradle") -apply from: rootProject.file('gradle/publish-npm-js.gradle') apply from: rootProject.file('gradle/dokka.gradle.kts') apply from: rootProject.file('gradle/publish.gradle') diff --git a/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin b/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin index 9d171f3a7a171bc5b861db55ab3a0011d91e155c..950dcf45bea0ec57b640ad8c5e4b7537fc13813a 100644 GIT binary patch delta 14 VcmX@bdy03%K4wPF&HI_TnE@(F1g!u7 delta 14 VcmX@bdy03%K4wOa&HI_TnE@(91gro6 diff --git a/kotlinx-coroutines-debug/api/kotlinx-coroutines-debug.api b/kotlinx-coroutines-debug/api/kotlinx-coroutines-debug.api index b671b1a488..11131fad42 100644 --- a/kotlinx-coroutines-debug/api/kotlinx-coroutines-debug.api +++ b/kotlinx-coroutines-debug/api/kotlinx-coroutines-debug.api @@ -39,6 +39,7 @@ public final class kotlinx/coroutines/debug/State : java/lang/Enum { public static final field CREATED Lkotlinx/coroutines/debug/State; public static final field RUNNING Lkotlinx/coroutines/debug/State; public static final field SUSPENDED Lkotlinx/coroutines/debug/State; + public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/debug/State; public static fun values ()[Lkotlinx/coroutines/debug/State; } From bb54504ed53b7000e7583fe3d412d179eba572e8 Mon Sep 17 00:00:00 2001 From: Qinglin Date: Fri, 28 Jul 2023 02:03:01 +0800 Subject: [PATCH 02/53] Fix typo in CommunityProjectsBuild.kt build script (#3829) substitues -> substitutes --- buildSrc/src/main/kotlin/CommunityProjectsBuild.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/CommunityProjectsBuild.kt b/buildSrc/src/main/kotlin/CommunityProjectsBuild.kt index 155c9e48ac..81a25f8d96 100644 --- a/buildSrc/src/main/kotlin/CommunityProjectsBuild.kt +++ b/buildSrc/src/main/kotlin/CommunityProjectsBuild.kt @@ -15,7 +15,7 @@ private val LOGGER: Logger = Logger.getLogger("Kotlin settings logger") * Functions in this file are responsible for configuring kotlinx.coroutines build against a custom dev version * of Kotlin compiler. * Such configuration is used in a composite community build of Kotlin in order to check whether not-yet-released changes - * are compatible with our libraries (aka "integration testing that substitues lack of unit testing"). + * are compatible with our libraries (aka "integration testing that substitutes lack of unit testing"). */ /** From e0dd2773c5ebbccc4bbdcdbf7bc418882d9d1e04 Mon Sep 17 00:00:00 2001 From: mvicsokolova <82594708+mvicsokolova@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:47:17 +0200 Subject: [PATCH 03/53] Conditionally remove native targets that are removed in 1.9.20 (#3825) --- buildSrc/src/main/kotlin/KotlinVersion.kt | 14 ++++++++++++++ gradle/compile-native-multiplatform.gradle | 13 +++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 buildSrc/src/main/kotlin/KotlinVersion.kt diff --git a/buildSrc/src/main/kotlin/KotlinVersion.kt b/buildSrc/src/main/kotlin/KotlinVersion.kt new file mode 100644 index 0000000000..5ac051ecad --- /dev/null +++ b/buildSrc/src/main/kotlin/KotlinVersion.kt @@ -0,0 +1,14 @@ +@file:JvmName("KotlinVersion") + +fun isKotlinVersionAtLeast(kotlinVersion: String, atLeastMajor: Int, atLeastMinor: Int, atLeastPatch: Int): Boolean { + val (major, minor) = kotlinVersion + .split('.') + .take(2) + .map { it.toInt() } + val patch = kotlinVersion.substringAfterLast('.').substringBefore('-').toInt() + return when { + major > atLeastMajor -> true + major < atLeastMajor -> false + else -> (minor == atLeastMinor && patch >= atLeastPatch) || minor > atLeastMinor + } +} diff --git a/gradle/compile-native-multiplatform.gradle b/gradle/compile-native-multiplatform.gradle index 3b2758854f..75e808bad8 100644 --- a/gradle/compile-native-multiplatform.gradle +++ b/gradle/compile-native-multiplatform.gradle @@ -1,3 +1,5 @@ +import static KotlinVersion.isKotlinVersionAtLeast + /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ @@ -5,6 +7,11 @@ project.ext.nativeMainSets = [] project.ext.nativeTestSets = [] +// TODO: this block should be removed when Kotlin version is updated to 1.9.20 +// Right now it is used for conditional removal of native targets that will be removed in 1.9.20 (iosArm32, watchosX86) +// and is necessary for testing of Kotlin dev builds. +def enableDeprecatedNativeTargets = !isKotlinVersionAtLeast(ext.kotlin_version, 1, 9, 20) + kotlin { targets { delegate.metaClass.addTarget = { preset -> @@ -43,8 +50,10 @@ kotlin { addTarget(presets.watchosDeviceArm64) // Deprecated, but were provided by coroutine; can be removed only when K/N drops the target - addTarget(presets.iosArm32) - addTarget(presets.watchosX86) + if (enableDeprecatedNativeTargets) { + addTarget(presets.iosArm32) + addTarget(presets.watchosX86) + } } sourceSets { From 6fb30e2420041b12ecee59b9d420e4a219b4b2df Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Thu, 26 Jan 2023 17:19:01 +0200 Subject: [PATCH 04/53] Add optins required in Kotlin 1.9 --- .../jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt | 9 +++++++-- .../benchmarks/scheduler/StatefulAwaitsBenchmark.kt | 1 + .../kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt | 1 + docs/topics/shared-mutable-state-and-concurrency.md | 3 ++- kotlinx-coroutines-core/common/src/EventLoop.common.kt | 1 + kotlinx-coroutines-core/jdk8/src/future/Future.kt | 2 +- kotlinx-coroutines-core/jvm/src/CoroutineContext.kt | 1 + kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt | 2 ++ .../jvm/src/internal/ResizableAtomicArray.kt | 1 + .../jvm/src/scheduling/CoroutineScheduler.kt | 4 +++- .../jvm/test/CancellableContinuationJvmTest.kt | 2 ++ .../jvm/test/ConcurrentTestUtilities.kt | 1 + kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt | 6 +++++- .../jvm/test/JobHandlersUpgradeStressTest.kt | 4 +++- kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt | 8 ++++++-- .../ChannelUndeliveredElementSelectOldStressTest.kt | 1 + .../test/channels/ChannelUndeliveredElementStressTest.kt | 1 + .../jvm/test/flow/CallbackFlowTest.kt | 1 + .../jvm/test/guide/example-sync-02.kt | 3 ++- .../jvm/test/scheduling/WorkQueueStressTest.kt | 1 + reactive/kotlinx-coroutines-reactive/src/Publish.kt | 1 + reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt | 1 + reactive/kotlinx-coroutines-reactor/src/Mono.kt | 1 + .../src/AndroidExceptionPreHandler.kt | 1 + ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt | 2 ++ 25 files changed, 49 insertions(+), 10 deletions(-) diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt index 20bdfa349a..e3b5aa9328 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt @@ -110,8 +110,13 @@ open class ForkJoinBenchmark : ParametrizedDispatcherBase() { } } - class RecursiveAction(val coefficients: LongArray, val start: Int, val end: Int, @Volatile var result: Double = 0.0, - parent: RecursiveAction? = null) : CountedCompleter(parent) { + class RecursiveAction( + val coefficients: LongArray, + val start: Int, + val end: Int, + @Volatile @OptIn(ExperimentalStdlibApi::class) var result: Double = 0.0, + parent: RecursiveAction? = null + ) : CountedCompleter(parent) { private var first: ForkJoinTask? = null private var second: ForkJoinTask? = null diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt index c5b34eda90..4f5e6cbb7d 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt @@ -57,6 +57,7 @@ open class StatefulAsyncBenchmark : ParametrizedDispatcherBase() { override var dispatcher: String = "fjp" @Volatile + @OptIn(ExperimentalStdlibApi::class) private var state: Array? = null @Setup diff --git a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt index 9654b6dabe..b70566a8b1 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt @@ -19,6 +19,7 @@ open class SimpleChannelBenchmark { private val iterations = 10_000 @Volatile + @OptIn(ExperimentalStdlibApi::class) private var sink: Int = 0 @Benchmark diff --git a/docs/topics/shared-mutable-state-and-concurrency.md b/docs/topics/shared-mutable-state-and-concurrency.md index fad13d64bc..0c0da99179 100644 --- a/docs/topics/shared-mutable-state-and-concurrency.md +++ b/docs/topics/shared-mutable-state-and-concurrency.md @@ -106,7 +106,8 @@ suspend fun massiveRun(action: suspend () -> Unit) { } //sampleStart -@Volatile // in Kotlin `volatile` is an annotation +@OptIn(ExperimentalStdlibApi::class) +@Volatile // in Kotlin `volatile` is an annotation var counter = 0 fun main() = runBlocking { diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt index 8d9eed21bc..b098ccd167 100644 --- a/kotlinx-coroutines-core/common/src/EventLoop.common.kt +++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt @@ -412,6 +412,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay { @JvmField var nanoTime: Long ) : Runnable, Comparable, DisposableHandle, ThreadSafeHeapNode, SynchronizedObject() { @Volatile + @OptIn(ExperimentalStdlibApi::class) private var _heap: Any? = null // null | ThreadSafeHeap | DISPOSED_TASK override var heap: ThreadSafeHeap<*>? diff --git a/kotlinx-coroutines-core/jdk8/src/future/Future.kt b/kotlinx-coroutines-core/jdk8/src/future/Future.kt index f7b4fdca0d..4882147166 100644 --- a/kotlinx-coroutines-core/jdk8/src/future/Future.kt +++ b/kotlinx-coroutines-core/jdk8/src/future/Future.kt @@ -180,7 +180,7 @@ public suspend fun CompletionStage.await(): T { } private class ContinuationHandler( - @Volatile @JvmField var cont: Continuation? + @Volatile @OptIn(ExperimentalStdlibApi::class) @JvmField var cont: Continuation? ) : BiFunction { @Suppress("UNCHECKED_CAST") override fun apply(result: T?, exception: Throwable?) { diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index b736125d35..41080e84f0 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -213,6 +213,7 @@ internal actual class UndispatchedCoroutineactual constructor ( * in another. */ @Volatile + @OptIn(ExperimentalStdlibApi::class) private var threadLocalIsSet = false init { diff --git a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt index 4d23aff3f7..eb6a7e29f0 100644 --- a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt +++ b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt @@ -44,6 +44,7 @@ internal actual object DefaultExecutor : EventLoopImplBase(), Runnable { @Suppress("ObjectPropertyName") @Volatile + @OptIn(ExperimentalStdlibApi::class) private var _thread: Thread? = null override val thread: Thread @@ -56,6 +57,7 @@ internal actual object DefaultExecutor : EventLoopImplBase(), Runnable { private const val SHUTDOWN = 4 @Volatile + @OptIn(ExperimentalStdlibApi::class) private var debugStatus: Int = FRESH private val isShutDown: Boolean get() = debugStatus == SHUTDOWN diff --git a/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt b/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt index 6c98a9dad9..317fdb3402 100644 --- a/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt @@ -12,6 +12,7 @@ import java.util.concurrent.atomic.* */ internal class ResizableAtomicArray(initialLength: Int) { @Volatile + @OptIn(ExperimentalStdlibApi::class) private var array = AtomicReferenceArray(initialLength) // for debug output diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt index 5ca3de5726..b67b92aad1 100644 --- a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt +++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt @@ -596,7 +596,8 @@ internal class CoroutineScheduler( } // guarded by scheduler lock, index in workers array, 0 when not in array (terminated) - @Volatile // volatile for push/pop operation into parkedWorkersStack + @Volatile + @OptIn(ExperimentalStdlibApi::class) // volatile for push/pop operation into parkedWorkersStack var indexInArray = 0 set(index) { name = "$schedulerName-worker-${if (index == 0) "TERMINATED" else index.toString()}" @@ -647,6 +648,7 @@ internal class CoroutineScheduler( * This reference is set to [NOT_IN_STACK] when worker is physically not in stack. */ @Volatile + @OptIn(ExperimentalStdlibApi::class) var nextParkedWorker: Any? = NOT_IN_STACK /* diff --git a/kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt b/kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt index f1a957adca..a9c1d94066 100644 --- a/kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt +++ b/kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt @@ -63,9 +63,11 @@ class CancellableContinuationJvmTest : TestBase() { private class BlockingSource { @Volatile + @OptIn(ExperimentalStdlibApi::class) private var isCancelled = false @Volatile + @OptIn(ExperimentalStdlibApi::class) public var hasSubscriber = false public fun subscribe() { diff --git a/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt b/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt index 4ccb74b427..1ca0c0142c 100644 --- a/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt +++ b/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt @@ -17,6 +17,7 @@ actual fun randomWait() { private object BlackHole { @Volatile + @OptIn(ExperimentalStdlibApi::class) var sink = 1 } diff --git a/kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt index 3a074f1568..cd04a1ad20 100644 --- a/kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt @@ -14,13 +14,17 @@ class JobDisposeStressTest: TestBase() { private val TEST_DURATION = 3 * stressTestMultiplier // seconds @Volatile + @OptIn(ExperimentalStdlibApi::class) private var done = false @Volatile + @OptIn(ExperimentalStdlibApi::class) private var job: TestJob? = null @Volatile + @OptIn(ExperimentalStdlibApi::class) private var handle: DisposableHandle? = null @Volatile + @OptIn(ExperimentalStdlibApi::class) private var exception: Throwable? = null private fun testThread(name: String, block: () -> Unit): Thread = @@ -77,4 +81,4 @@ class JobDisposeStressTest: TestBase() { @Suppress("DEPRECATION_ERROR") private class TestJob : JobSupport(active = true) -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt index 0a03247d34..8e1208df40 100644 --- a/kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt @@ -24,9 +24,11 @@ class JobHandlersUpgradeStressTest : TestBase() { private val sink = atomic(0) @Volatile + @OptIn(ExperimentalStdlibApi::class) private var done = false @Volatile + @OptIn(ExperimentalStdlibApi::class) private var job: Job? = null internal class State { @@ -94,4 +96,4 @@ class JobHandlersUpgradeStressTest : TestBase() { println(" Fired handler ${fired.value} times") } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt index b4bc96ebdd..f356d0b1b2 100644 --- a/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt +++ b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt @@ -27,9 +27,11 @@ internal inline fun withVirtualTimeSource(log: PrintStream? = null, block: () -> private const val NOT_PARKED = -1L private class ThreadStatus { - @Volatile @JvmField + @Volatile + @OptIn(ExperimentalStdlibApi::class) @JvmField var parkedTill = NOT_PARKED - @Volatile @JvmField + @Volatile + @OptIn(ExperimentalStdlibApi::class) @JvmField var permit = false var registered = 0 override fun toString(): String = "parkedTill = ${TimeUnit.NANOSECONDS.toMillis(parkedTill)} ms, permit = $permit" @@ -47,9 +49,11 @@ internal class VirtualTimeSource( private var checkpointNanos: Long = System.nanoTime() @Volatile + @OptIn(ExperimentalStdlibApi::class) private var isShutdown = false @Volatile + @OptIn(ExperimentalStdlibApi::class) private var time: Long = 0 private var trackedTasks = 0 diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt index 25cccf948a..ee4c5df160 100644 --- a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt @@ -40,6 +40,7 @@ class ChannelUndeliveredElementSelectOldStressTest(private val kind: TestChannel private val receiverDone = Channel(1) @Volatile + @OptIn(ExperimentalStdlibApi::class) private var lastReceived = -1L private var stoppedSender = 0L diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt index f8a5644769..3030587478 100644 --- a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt @@ -40,6 +40,7 @@ class ChannelUndeliveredElementStressTest(private val kind: TestChannelKind) : T private val receiverDone = Channel(1) @Volatile + @OptIn(ExperimentalStdlibApi::class) private var lastReceived = -1L private var stoppedSender = 0L diff --git a/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt b/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt index f1be284cae..be32d2d7f0 100644 --- a/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt +++ b/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt @@ -15,6 +15,7 @@ class CallbackFlowTest : TestBase() { private class CallbackApi(val block: (SendChannel) -> Unit) { var started = false @Volatile + @OptIn(ExperimentalStdlibApi::class) var stopped = false lateinit var thread: Thread diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt index fb551f8953..b17ca91252 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt @@ -23,7 +23,8 @@ suspend fun massiveRun(action: suspend () -> Unit) { println("Completed ${n * k} actions in $time ms") } -@Volatile // in Kotlin `volatile` is an annotation +@OptIn(ExperimentalStdlibApi::class) +@Volatile // in Kotlin `volatile` is an annotation var counter = 0 fun main() = runBlocking { diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt index e2562b57ba..0004aa1788 100644 --- a/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt @@ -22,6 +22,7 @@ class WorkQueueStressTest : TestBase() { private val producerQueue = WorkQueue() @Volatile + @OptIn(ExperimentalStdlibApi::class) private var producerFinished = false @Before diff --git a/reactive/kotlinx-coroutines-reactive/src/Publish.kt b/reactive/kotlinx-coroutines-reactive/src/Publish.kt index ae85e4186a..1770eb6be9 100644 --- a/reactive/kotlinx-coroutines-reactive/src/Publish.kt +++ b/reactive/kotlinx-coroutines-reactive/src/Publish.kt @@ -74,6 +74,7 @@ public class PublisherCoroutine( private val _nRequested = atomic(0L) // < 0 when closed (CLOSED or SIGNALLED) @Volatile + @OptIn(ExperimentalStdlibApi::class) private var cancelled = false // true after Subscription.cancel() is invoked override val isClosedForSend: Boolean get() = !isActive diff --git a/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt b/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt index 1a527a3c2b..2c328e60f5 100644 --- a/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt +++ b/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt @@ -197,6 +197,7 @@ public class FlowSubscription( private val requested = atomic(0L) private val producer = atomic?>(createInitialContinuation()) @Volatile + @OptIn(ExperimentalStdlibApi::class) private var cancellationRequested = false // This code wraps startCoroutineCancellable into continuation diff --git a/reactive/kotlinx-coroutines-reactor/src/Mono.kt b/reactive/kotlinx-coroutines-reactor/src/Mono.kt index 27dea603e9..76f7d1e392 100644 --- a/reactive/kotlinx-coroutines-reactor/src/Mono.kt +++ b/reactive/kotlinx-coroutines-reactor/src/Mono.kt @@ -97,6 +97,7 @@ private class MonoCoroutine( private val sink: MonoSink ) : AbstractCoroutine(parentContext, false, true), Disposable { @Volatile + @OptIn(ExperimentalStdlibApi::class) private var disposed = false override fun onCompleted(value: T) { diff --git a/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt b/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt index 0bc603ea1e..219f164ae7 100644 --- a/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt +++ b/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt @@ -13,6 +13,7 @@ internal class AndroidExceptionPreHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler { @Volatile + @OptIn(ExperimentalStdlibApi::class) private var _preHandler: Any? = this // uninitialized marker // Reflectively lookup pre-handler. diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt index 7012c23ecd..3bf0e83134 100644 --- a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt +++ b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt @@ -128,6 +128,7 @@ internal class HandlerContext private constructor( ) : this(handler, name, false) @Volatile + @OptIn(ExperimentalStdlibApi::class) private var _immediate: HandlerContext? = if (invokeImmediately) this else null override val immediate: HandlerContext = _immediate ?: @@ -176,6 +177,7 @@ internal class HandlerContext private constructor( override fun hashCode(): Int = System.identityHashCode(handler) } +@OptIn(ExperimentalStdlibApi::class) @Volatile private var choreographer: Choreographer? = null From 77b630c6492b462af9262a9529d4530f971452c9 Mon Sep 17 00:00:00 2001 From: mvicsokolova Date: Thu, 29 Jun 2023 16:03:55 +0200 Subject: [PATCH 05/53] Migrate from the deprecated native atomics This commit is required for the aggregate build that checks the change of deprecation level for native atomics (https://jetbrains.team/p/kt/reviews/10650/timeline). Usages of native atomics from kotlin.native.concurrent package are replaced with new atomics from kotlin.concurrent because old native atomics will be deprecated with error. NOTE: this commit is not present in develop branch, because new kotlin.concurrent atomics are only available since Kotlin 1.9.0. IT SHOULD BE REMOVED after kotlinx.coroutines updates Kotlin version to 1.9.0. --- kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt | 3 ++- kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt index 007d079a8d..11627ba399 100644 --- a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt +++ b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt @@ -8,7 +8,8 @@ import kotlinx.atomicfu.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.internal.* import kotlin.coroutines.* -import kotlin.native.concurrent.* +import kotlin.concurrent.AtomicReference +import kotlin.native.concurrent.Worker import kotlin.time.* import kotlin.time.Duration.Companion.milliseconds diff --git a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt index edc0a13ce8..ab8a0bd4c7 100644 --- a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt @@ -8,7 +8,7 @@ import kotlinx.cinterop.* import platform.CoreFoundation.* import platform.darwin.* import kotlin.coroutines.* -import kotlin.native.concurrent.* +import kotlin.concurrent.* import kotlin.native.internal.NativePtr internal fun isMainThread(): Boolean = CFRunLoopGetCurrent() == CFRunLoopGetMain() From bb21e8ebfb297e808690c9179eaf598232b587ef Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Fri, 28 Jul 2023 16:49:51 +0200 Subject: [PATCH 06/53] Ignore adding additional optIns also for "jvmCoreMain" source set After changes required for https://youtrack.jetbrains.com/issue/KT-57292/Rework-configuration-of-compiler-settings-in-MPP-Projects consistency checks were move later in configuration phase. Now they started also catch inconsistency between "jvmMain" and "jvmCoreMain" source sets. --- kotlinx-coroutines-core/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index 7724d190af..b21f1cbfd5 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -233,7 +233,7 @@ kotlin.sourceSets { kotlin.sourceSets.configureEach { // Do not apply 'ExperimentalForeignApi' where we have allWarningsAsErrors set - if (it.name in ["jvmMain", "jsMain", "concurrentMain", "commonMain"]) return + if (it.name in ["jvmMain", "jvmCoreMain", "jsMain", "concurrentMain", "commonMain"]) return languageSettings { optIn('kotlinx.cinterop.ExperimentalForeignApi') optIn('kotlin.experimental.ExperimentalNativeApi') From a1134f2c5399b05d73067f39526dee631243e722 Mon Sep 17 00:00:00 2001 From: mvicsokolova Date: Sat, 29 Jul 2023 21:18:25 +0200 Subject: [PATCH 07/53] Revert "Add optins required in Kotlin 1.9" This reverts commit 6fb30e2420041b12ecee59b9d420e4a219b4b2df. --- .../jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt | 9 ++------- .../benchmarks/scheduler/StatefulAwaitsBenchmark.kt | 1 - .../kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt | 1 - docs/topics/shared-mutable-state-and-concurrency.md | 3 +-- kotlinx-coroutines-core/common/src/EventLoop.common.kt | 1 - kotlinx-coroutines-core/jdk8/src/future/Future.kt | 2 +- kotlinx-coroutines-core/jvm/src/CoroutineContext.kt | 1 - kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt | 2 -- .../jvm/src/internal/ResizableAtomicArray.kt | 1 - .../jvm/src/scheduling/CoroutineScheduler.kt | 4 +--- .../jvm/test/CancellableContinuationJvmTest.kt | 2 -- .../jvm/test/ConcurrentTestUtilities.kt | 1 - kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt | 6 +----- .../jvm/test/JobHandlersUpgradeStressTest.kt | 4 +--- kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt | 8 ++------ .../ChannelUndeliveredElementSelectOldStressTest.kt | 1 - .../test/channels/ChannelUndeliveredElementStressTest.kt | 1 - .../jvm/test/flow/CallbackFlowTest.kt | 1 - .../jvm/test/guide/example-sync-02.kt | 3 +-- .../jvm/test/scheduling/WorkQueueStressTest.kt | 1 - reactive/kotlinx-coroutines-reactive/src/Publish.kt | 1 - reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt | 1 - reactive/kotlinx-coroutines-reactor/src/Mono.kt | 1 - .../src/AndroidExceptionPreHandler.kt | 1 - ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt | 2 -- 25 files changed, 10 insertions(+), 49 deletions(-) diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt index e3b5aa9328..20bdfa349a 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/ForkJoinBenchmark.kt @@ -110,13 +110,8 @@ open class ForkJoinBenchmark : ParametrizedDispatcherBase() { } } - class RecursiveAction( - val coefficients: LongArray, - val start: Int, - val end: Int, - @Volatile @OptIn(ExperimentalStdlibApi::class) var result: Double = 0.0, - parent: RecursiveAction? = null - ) : CountedCompleter(parent) { + class RecursiveAction(val coefficients: LongArray, val start: Int, val end: Int, @Volatile var result: Double = 0.0, + parent: RecursiveAction? = null) : CountedCompleter(parent) { private var first: ForkJoinTask? = null private var second: ForkJoinTask? = null diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt index 4f5e6cbb7d..c5b34eda90 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/StatefulAwaitsBenchmark.kt @@ -57,7 +57,6 @@ open class StatefulAsyncBenchmark : ParametrizedDispatcherBase() { override var dispatcher: String = "fjp" @Volatile - @OptIn(ExperimentalStdlibApi::class) private var state: Array? = null @Setup diff --git a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt index b70566a8b1..9654b6dabe 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt @@ -19,7 +19,6 @@ open class SimpleChannelBenchmark { private val iterations = 10_000 @Volatile - @OptIn(ExperimentalStdlibApi::class) private var sink: Int = 0 @Benchmark diff --git a/docs/topics/shared-mutable-state-and-concurrency.md b/docs/topics/shared-mutable-state-and-concurrency.md index 0c0da99179..fad13d64bc 100644 --- a/docs/topics/shared-mutable-state-and-concurrency.md +++ b/docs/topics/shared-mutable-state-and-concurrency.md @@ -106,8 +106,7 @@ suspend fun massiveRun(action: suspend () -> Unit) { } //sampleStart -@OptIn(ExperimentalStdlibApi::class) -@Volatile // in Kotlin `volatile` is an annotation +@Volatile // in Kotlin `volatile` is an annotation var counter = 0 fun main() = runBlocking { diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt index b098ccd167..8d9eed21bc 100644 --- a/kotlinx-coroutines-core/common/src/EventLoop.common.kt +++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt @@ -412,7 +412,6 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay { @JvmField var nanoTime: Long ) : Runnable, Comparable, DisposableHandle, ThreadSafeHeapNode, SynchronizedObject() { @Volatile - @OptIn(ExperimentalStdlibApi::class) private var _heap: Any? = null // null | ThreadSafeHeap | DISPOSED_TASK override var heap: ThreadSafeHeap<*>? diff --git a/kotlinx-coroutines-core/jdk8/src/future/Future.kt b/kotlinx-coroutines-core/jdk8/src/future/Future.kt index 4882147166..f7b4fdca0d 100644 --- a/kotlinx-coroutines-core/jdk8/src/future/Future.kt +++ b/kotlinx-coroutines-core/jdk8/src/future/Future.kt @@ -180,7 +180,7 @@ public suspend fun CompletionStage.await(): T { } private class ContinuationHandler( - @Volatile @OptIn(ExperimentalStdlibApi::class) @JvmField var cont: Continuation? + @Volatile @JvmField var cont: Continuation? ) : BiFunction { @Suppress("UNCHECKED_CAST") override fun apply(result: T?, exception: Throwable?) { diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index 41080e84f0..b736125d35 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -213,7 +213,6 @@ internal actual class UndispatchedCoroutineactual constructor ( * in another. */ @Volatile - @OptIn(ExperimentalStdlibApi::class) private var threadLocalIsSet = false init { diff --git a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt index eb6a7e29f0..4d23aff3f7 100644 --- a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt +++ b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt @@ -44,7 +44,6 @@ internal actual object DefaultExecutor : EventLoopImplBase(), Runnable { @Suppress("ObjectPropertyName") @Volatile - @OptIn(ExperimentalStdlibApi::class) private var _thread: Thread? = null override val thread: Thread @@ -57,7 +56,6 @@ internal actual object DefaultExecutor : EventLoopImplBase(), Runnable { private const val SHUTDOWN = 4 @Volatile - @OptIn(ExperimentalStdlibApi::class) private var debugStatus: Int = FRESH private val isShutDown: Boolean get() = debugStatus == SHUTDOWN diff --git a/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt b/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt index 317fdb3402..6c98a9dad9 100644 --- a/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt @@ -12,7 +12,6 @@ import java.util.concurrent.atomic.* */ internal class ResizableAtomicArray(initialLength: Int) { @Volatile - @OptIn(ExperimentalStdlibApi::class) private var array = AtomicReferenceArray(initialLength) // for debug output diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt index b67b92aad1..5ca3de5726 100644 --- a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt +++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt @@ -596,8 +596,7 @@ internal class CoroutineScheduler( } // guarded by scheduler lock, index in workers array, 0 when not in array (terminated) - @Volatile - @OptIn(ExperimentalStdlibApi::class) // volatile for push/pop operation into parkedWorkersStack + @Volatile // volatile for push/pop operation into parkedWorkersStack var indexInArray = 0 set(index) { name = "$schedulerName-worker-${if (index == 0) "TERMINATED" else index.toString()}" @@ -648,7 +647,6 @@ internal class CoroutineScheduler( * This reference is set to [NOT_IN_STACK] when worker is physically not in stack. */ @Volatile - @OptIn(ExperimentalStdlibApi::class) var nextParkedWorker: Any? = NOT_IN_STACK /* diff --git a/kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt b/kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt index a9c1d94066..f1a957adca 100644 --- a/kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt +++ b/kotlinx-coroutines-core/jvm/test/CancellableContinuationJvmTest.kt @@ -63,11 +63,9 @@ class CancellableContinuationJvmTest : TestBase() { private class BlockingSource { @Volatile - @OptIn(ExperimentalStdlibApi::class) private var isCancelled = false @Volatile - @OptIn(ExperimentalStdlibApi::class) public var hasSubscriber = false public fun subscribe() { diff --git a/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt b/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt index 1ca0c0142c..4ccb74b427 100644 --- a/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt +++ b/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt @@ -17,7 +17,6 @@ actual fun randomWait() { private object BlackHole { @Volatile - @OptIn(ExperimentalStdlibApi::class) var sink = 1 } diff --git a/kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt index cd04a1ad20..3a074f1568 100644 --- a/kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/JobDisposeStressTest.kt @@ -14,17 +14,13 @@ class JobDisposeStressTest: TestBase() { private val TEST_DURATION = 3 * stressTestMultiplier // seconds @Volatile - @OptIn(ExperimentalStdlibApi::class) private var done = false @Volatile - @OptIn(ExperimentalStdlibApi::class) private var job: TestJob? = null @Volatile - @OptIn(ExperimentalStdlibApi::class) private var handle: DisposableHandle? = null @Volatile - @OptIn(ExperimentalStdlibApi::class) private var exception: Throwable? = null private fun testThread(name: String, block: () -> Unit): Thread = @@ -81,4 +77,4 @@ class JobDisposeStressTest: TestBase() { @Suppress("DEPRECATION_ERROR") private class TestJob : JobSupport(active = true) -} +} \ No newline at end of file diff --git a/kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt index 8e1208df40..0a03247d34 100644 --- a/kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/JobHandlersUpgradeStressTest.kt @@ -24,11 +24,9 @@ class JobHandlersUpgradeStressTest : TestBase() { private val sink = atomic(0) @Volatile - @OptIn(ExperimentalStdlibApi::class) private var done = false @Volatile - @OptIn(ExperimentalStdlibApi::class) private var job: Job? = null internal class State { @@ -96,4 +94,4 @@ class JobHandlersUpgradeStressTest : TestBase() { println(" Fired handler ${fired.value} times") } -} +} \ No newline at end of file diff --git a/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt index f356d0b1b2..b4bc96ebdd 100644 --- a/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt +++ b/kotlinx-coroutines-core/jvm/test/VirtualTimeSource.kt @@ -27,11 +27,9 @@ internal inline fun withVirtualTimeSource(log: PrintStream? = null, block: () -> private const val NOT_PARKED = -1L private class ThreadStatus { - @Volatile - @OptIn(ExperimentalStdlibApi::class) @JvmField + @Volatile @JvmField var parkedTill = NOT_PARKED - @Volatile - @OptIn(ExperimentalStdlibApi::class) @JvmField + @Volatile @JvmField var permit = false var registered = 0 override fun toString(): String = "parkedTill = ${TimeUnit.NANOSECONDS.toMillis(parkedTill)} ms, permit = $permit" @@ -49,11 +47,9 @@ internal class VirtualTimeSource( private var checkpointNanos: Long = System.nanoTime() @Volatile - @OptIn(ExperimentalStdlibApi::class) private var isShutdown = false @Volatile - @OptIn(ExperimentalStdlibApi::class) private var time: Long = 0 private var trackedTasks = 0 diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt index ee4c5df160..25cccf948a 100644 --- a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt @@ -40,7 +40,6 @@ class ChannelUndeliveredElementSelectOldStressTest(private val kind: TestChannel private val receiverDone = Channel(1) @Volatile - @OptIn(ExperimentalStdlibApi::class) private var lastReceived = -1L private var stoppedSender = 0L diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt index 3030587478..f8a5644769 100644 --- a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt @@ -40,7 +40,6 @@ class ChannelUndeliveredElementStressTest(private val kind: TestChannelKind) : T private val receiverDone = Channel(1) @Volatile - @OptIn(ExperimentalStdlibApi::class) private var lastReceived = -1L private var stoppedSender = 0L diff --git a/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt b/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt index be32d2d7f0..f1be284cae 100644 --- a/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt +++ b/kotlinx-coroutines-core/jvm/test/flow/CallbackFlowTest.kt @@ -15,7 +15,6 @@ class CallbackFlowTest : TestBase() { private class CallbackApi(val block: (SendChannel) -> Unit) { var started = false @Volatile - @OptIn(ExperimentalStdlibApi::class) var stopped = false lateinit var thread: Thread diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt b/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt index b17ca91252..fb551f8953 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt @@ -23,8 +23,7 @@ suspend fun massiveRun(action: suspend () -> Unit) { println("Completed ${n * k} actions in $time ms") } -@OptIn(ExperimentalStdlibApi::class) -@Volatile // in Kotlin `volatile` is an annotation +@Volatile // in Kotlin `volatile` is an annotation var counter = 0 fun main() = runBlocking { diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt index 0004aa1788..e2562b57ba 100644 --- a/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt @@ -22,7 +22,6 @@ class WorkQueueStressTest : TestBase() { private val producerQueue = WorkQueue() @Volatile - @OptIn(ExperimentalStdlibApi::class) private var producerFinished = false @Before diff --git a/reactive/kotlinx-coroutines-reactive/src/Publish.kt b/reactive/kotlinx-coroutines-reactive/src/Publish.kt index 1770eb6be9..ae85e4186a 100644 --- a/reactive/kotlinx-coroutines-reactive/src/Publish.kt +++ b/reactive/kotlinx-coroutines-reactive/src/Publish.kt @@ -74,7 +74,6 @@ public class PublisherCoroutine( private val _nRequested = atomic(0L) // < 0 when closed (CLOSED or SIGNALLED) @Volatile - @OptIn(ExperimentalStdlibApi::class) private var cancelled = false // true after Subscription.cancel() is invoked override val isClosedForSend: Boolean get() = !isActive diff --git a/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt b/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt index 2c328e60f5..1a527a3c2b 100644 --- a/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt +++ b/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt @@ -197,7 +197,6 @@ public class FlowSubscription( private val requested = atomic(0L) private val producer = atomic?>(createInitialContinuation()) @Volatile - @OptIn(ExperimentalStdlibApi::class) private var cancellationRequested = false // This code wraps startCoroutineCancellable into continuation diff --git a/reactive/kotlinx-coroutines-reactor/src/Mono.kt b/reactive/kotlinx-coroutines-reactor/src/Mono.kt index 76f7d1e392..27dea603e9 100644 --- a/reactive/kotlinx-coroutines-reactor/src/Mono.kt +++ b/reactive/kotlinx-coroutines-reactor/src/Mono.kt @@ -97,7 +97,6 @@ private class MonoCoroutine( private val sink: MonoSink ) : AbstractCoroutine(parentContext, false, true), Disposable { @Volatile - @OptIn(ExperimentalStdlibApi::class) private var disposed = false override fun onCompleted(value: T) { diff --git a/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt b/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt index 219f164ae7..0bc603ea1e 100644 --- a/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt +++ b/ui/kotlinx-coroutines-android/src/AndroidExceptionPreHandler.kt @@ -13,7 +13,6 @@ internal class AndroidExceptionPreHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler { @Volatile - @OptIn(ExperimentalStdlibApi::class) private var _preHandler: Any? = this // uninitialized marker // Reflectively lookup pre-handler. diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt index 3bf0e83134..7012c23ecd 100644 --- a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt +++ b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt @@ -128,7 +128,6 @@ internal class HandlerContext private constructor( ) : this(handler, name, false) @Volatile - @OptIn(ExperimentalStdlibApi::class) private var _immediate: HandlerContext? = if (invokeImmediately) this else null override val immediate: HandlerContext = _immediate ?: @@ -177,7 +176,6 @@ internal class HandlerContext private constructor( override fun hashCode(): Int = System.identityHashCode(handler) } -@OptIn(ExperimentalStdlibApi::class) @Volatile private var choreographer: Choreographer? = null From 1781a1ea2fa2b513ea93b0224eda977d34e3a0b6 Mon Sep 17 00:00:00 2001 From: Margarita Bobova Date: Mon, 31 Jul 2023 13:52:00 +0200 Subject: [PATCH 08/53] Add Optin for kotlinx.cinterop.UnsafeNumber due to KT-59859 --- kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt index edc0a13ce8..fb9fd3c2fb 100644 --- a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt @@ -18,6 +18,7 @@ internal actual fun createMainDispatcher(default: CoroutineDispatcher): MainCoro internal actual fun createDefaultDispatcher(): CoroutineDispatcher = DarwinGlobalQueueDispatcher private object DarwinGlobalQueueDispatcher : CoroutineDispatcher() { + @OptIn(UnsafeNumber::class) override fun dispatch(context: CoroutineContext, block: Runnable) { autoreleasepool { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.convert(), 0u)) { @@ -76,6 +77,7 @@ private val TIMER_DISPOSED = NativePtr.NULL.plus(1) private class Timer : DisposableHandle { private val ref = AtomicNativePtr(TIMER_NEW) + @OptIn(UnsafeNumber::class) fun start(timeMillis: Long, timerBlock: TimerBlock) { val fireDate = CFAbsoluteTimeGetCurrent() + timeMillis / 1000.0 val timer = CFRunLoopTimerCreateWithHandler(null, fireDate, 0.0, 0u, 0, timerBlock) From db57e0881d47291704bd65fcc0bef0b7badcc770 Mon Sep 17 00:00:00 2001 From: Nikita Bobko <20517828+nikitabobko@users.noreply.github.com> Date: Wed, 9 Aug 2023 15:50:03 +0200 Subject: [PATCH 09/53] Fix NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS compilation error (#3826) The compilation error appeared after KT-59665 --- kotlinx-coroutines-core/jvm/test/TestBase.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt index f9e5466b44..83eb51f6e5 100644 --- a/kotlinx-coroutines-core/jvm/test/TestBase.kt +++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt @@ -54,6 +54,7 @@ public actual typealias TestResult = Unit * } * ``` */ +@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual open class TestBase(private var disableOutCheck: Boolean) { actual constructor(): this(false) From 9942a3080a81c447b31cb82b24e253570baf706d Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Fri, 11 Aug 2023 15:45:51 +0200 Subject: [PATCH 10/53] Fix expect/actual mismatched member scope for `open expect` compilation errors Those are compilation errors introduced in Kotlin 1.9.20 in scope of https://youtrack.jetbrains.com/issue/KT-22841 --- .../concurrent/src/CompletionHandler.kt | 8 ++++++ .../src/internal/LockFreeLinkedList.kt | 26 +++++++++++++++++- .../js/src/internal/LinkedList.kt | 9 ++++++- kotlinx-coroutines-core/jvm/src/EventLoop.kt | 6 +++++ kotlinx-coroutines-core/jvm/src/Executors.kt | 6 +++++ .../jvm/src/SchedulerTask.kt | 4 +++ kotlinx-coroutines-core/jvm/test/TestBase.kt | 27 ++++++++++++++++++- .../native/src/internal/Synchronized.kt | 2 ++ 8 files changed, 85 insertions(+), 3 deletions(-) diff --git a/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt b/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt index 4835f7968e..6751791942 100644 --- a/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt +++ b/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt @@ -6,12 +6,20 @@ package kotlinx.coroutines import kotlinx.coroutines.internal.* +// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 +// New 'CompletionHandler` supertype is added compared to the expect declaration. +// Probably we can add it to JS and common too, to avoid the suppression/opt-in +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER") internal actual abstract class CompletionHandlerBase actual constructor() : LockFreeLinkedListNode(), CompletionHandler { actual abstract override fun invoke(cause: Throwable?) } internal actual inline val CompletionHandlerBase.asHandler: CompletionHandler get() = this +// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 +// New 'CompletionHandler` supertype is added compared to the expect declaration. +// Probably we can add it to JS and common too, to avoid the suppression/opt-in +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER") internal actual abstract class CancelHandlerBase actual constructor() : CompletionHandler { actual abstract override fun invoke(cause: Throwable?) } diff --git a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt index 00888499c6..a692e26f7c 100644 --- a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt +++ b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt @@ -42,7 +42,11 @@ internal val CONDITION_FALSE: Any = Symbol("CONDITION_FALSE") * * @suppress **This is unstable API and it is subject to change.** */ -@Suppress("LeakingThis") +@Suppress( + "LeakingThis", + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER" +) @InternalCoroutinesApi public actual open class LockFreeLinkedListNode { private val _next = atomic(this) // Node | Removed | OpDescriptor @@ -68,14 +72,20 @@ public actual open class LockFreeLinkedListNode { } } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") @PublishedApi internal inline fun makeCondAddOp(node: Node, crossinline condition: () -> Boolean): CondAddOp = object : CondAddOp(node) { override fun prepare(affected: Node): Any? = if (condition()) null else CONDITION_FALSE } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("MODALITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION") public actual open val isRemoved: Boolean get() = next is Removed + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") // LINEARIZABLE. Returns Node | Removed public val next: Any get() { _next.loop { next -> @@ -166,6 +176,8 @@ public actual open class LockFreeLinkedListNode { * Where `==>` denotes linearization point. * Returns `false` if `next` was not following `this` node. */ + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") @PublishedApi internal fun addNext(node: Node, next: Node): Boolean { node._prev.lazySet(this) @@ -176,6 +188,8 @@ public actual open class LockFreeLinkedListNode { return true } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") // returns UNDECIDED, SUCCESS or FAILURE @PublishedApi internal fun tryCondAddNext(node: Node, next: Node, condAdd: CondAddOp): Int { @@ -199,6 +213,8 @@ public actual open class LockFreeLinkedListNode { public actual open fun remove(): Boolean = removeOrNext() == null + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") // returns null if removed successfully or next node if this node is already removed @PublishedApi internal fun removeOrNext(): Node? { @@ -255,6 +271,8 @@ public actual open class LockFreeLinkedListNode { } } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") protected open fun nextIfRemoved(): Node? = (next as? Removed)?.ref /** @@ -311,6 +329,8 @@ public actual open class LockFreeLinkedListNode { } } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") internal fun validateNode(prev: Node, next: Node) { assert { prev === this._prev.value } assert { next === this._next.value } @@ -331,6 +351,8 @@ internal fun Any.unwrap(): Node = (this as? Removed)?.ref ?: this as Node * * @suppress **This is unstable API and it is subject to change.** */ +// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER") public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() { public actual val isEmpty: Boolean get() = next === this @@ -352,6 +374,8 @@ public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() { override val isRemoved: Boolean get() = false override fun nextIfRemoved(): Node? = null + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") internal fun validate() { var prev: Node = this var cur: Node = next as Node diff --git a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt index de5d491121..1a1c346959 100644 --- a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt +++ b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt @@ -10,7 +10,14 @@ import kotlinx.coroutines.* private typealias Node = LinkedListNode /** @suppress **This is unstable API and it is subject to change.** */ -@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // :TODO: Remove when fixed: https://youtrack.jetbrains.com/issue/KT-23703 +@Suppress( + // :TODO: Remove when fixed: https://youtrack.jetbrains.com/issue/KT-23703 + "NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS", + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER", + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER" +) public actual typealias LockFreeLinkedListNode = LinkedListNode /** @suppress **This is unstable API and it is subject to change.** */ diff --git a/kotlinx-coroutines-core/jvm/src/EventLoop.kt b/kotlinx-coroutines-core/jvm/src/EventLoop.kt index 7d1078cf6f..7acefd8c89 100644 --- a/kotlinx-coroutines-core/jvm/src/EventLoop.kt +++ b/kotlinx-coroutines-core/jvm/src/EventLoop.kt @@ -8,7 +8,11 @@ import kotlinx.coroutines.Runnable import kotlinx.coroutines.scheduling.* import kotlinx.coroutines.scheduling.CoroutineScheduler +// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER") internal actual abstract class EventLoopImplPlatform: EventLoop() { + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") protected abstract val thread: Thread protected actual fun unpark() { @@ -17,6 +21,8 @@ internal actual abstract class EventLoopImplPlatform: EventLoop() { unpark(thread) } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("MODALITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION") protected actual open fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask) { DefaultExecutor.schedule(now, delayedTask) } diff --git a/kotlinx-coroutines-core/jvm/src/Executors.kt b/kotlinx-coroutines-core/jvm/src/Executors.kt index 121ba3f443..0449c338fd 100644 --- a/kotlinx-coroutines-core/jvm/src/Executors.kt +++ b/kotlinx-coroutines-core/jvm/src/Executors.kt @@ -37,6 +37,12 @@ public abstract class ExecutorCoroutineDispatcher: CoroutineDispatcher(), Closea public abstract override fun close() } +@Suppress( + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER", + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER" +) @ExperimentalCoroutinesApi public actual typealias CloseableCoroutineDispatcher = ExecutorCoroutineDispatcher diff --git a/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt b/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt index 6a00f45f13..a946f8173d 100644 --- a/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt +++ b/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt @@ -6,8 +6,12 @@ package kotlinx.coroutines import kotlinx.coroutines.scheduling.* +// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER") internal actual typealias SchedulerTask = Task +// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER") internal actual typealias SchedulerTaskContext = TaskContext @Suppress("EXTENSION_SHADOWED_BY_MEMBER") diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt index 83eb51f6e5..3098138663 100644 --- a/kotlinx-coroutines-core/jvm/test/TestBase.kt +++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt @@ -54,7 +54,12 @@ public actual typealias TestResult = Unit * } * ``` */ -@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") +@Suppress( + // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") + "NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS", + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER" +) public actual open class TestBase(private var disableOutCheck: Boolean) { actual constructor(): this(false) @@ -80,11 +85,14 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { * Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not * complete successfully even if this exception is consumed somewhere in the test. */ + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun error(message: Any, cause: Throwable? = null): Nothing { throw makeError(message, cause) } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") public fun hasError() = error.get() != null private fun makeError(message: Any, cause: Throwable? = null): IllegalStateException = @@ -108,6 +116,8 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { * Throws [IllegalStateException] when `value` is false like `check` in stdlib, but also ensures that the * test will not complete successfully even if this exception is consumed somewhere in the test. */ + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") public inline fun check(value: Boolean, lazyMessage: () -> Any) { if (!value) error(lazyMessage()) } @@ -155,12 +165,16 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { } }) + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") fun println(message: Any?) { if (disableOutCheck) kotlin.io.println(message) else previousOut.println(message) } @Before + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") fun before() { initPoolsBeforeTest() threadsBefore = currentThreads() @@ -176,6 +190,8 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { } } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") @After fun onCompletion() { // onCompletion should not throw exceptions before it finishes all cleanup, so that other tests always @@ -203,16 +219,21 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { error.get()?.let { throw it } } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") fun initPoolsBeforeTest() { DefaultScheduler.usePrivateScheduler() } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") fun shutdownPoolsAfterTest() { DefaultScheduler.shutdown(SHUTDOWN_TIMEOUT) DefaultExecutor.shutdownForTests(SHUTDOWN_TIMEOUT) DefaultScheduler.restore() } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 @Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun runTest( expected: ((Throwable) -> Boolean)? = null, @@ -247,12 +268,16 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { error("Too few unhandled exceptions $exCount, expected ${unhandled.size}") } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") protected inline fun assertFailsWith(block: () -> Unit): T { val result = runCatching(block) assertTrue(result.exceptionOrNull() is T, "Expected ${T::class}, but had $result") return result.exceptionOrNull()!! as T } + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") protected suspend fun currentDispatcher() = coroutineContext[ContinuationInterceptor]!! } diff --git a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt index 8a8ecfe393..7adcec12ba 100644 --- a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt +++ b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt @@ -11,6 +11,8 @@ import kotlinx.atomicfu.locks.withLock as withLock2 * @suppress **This an internal API and should not be used from general code.** */ @InternalCoroutinesApi +// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER") public actual typealias SynchronizedObject = kotlinx.atomicfu.locks.SynchronizedObject /** From 203ab5b7dbc87705c36387823d782163de7167a2 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 14 Aug 2023 18:53:48 +0200 Subject: [PATCH 11/53] Explain potential memory leak that nulling BufferedChannelIterator.continuation out prevents (#3837) --- .../common/src/channels/BufferedChannel.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt index 4fc7d4384d..08cda7e9e8 100644 --- a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt @@ -1690,8 +1690,11 @@ internal open class BufferedChannel( } fun tryResumeHasNextOnClosedChannel() { - // Read the current continuation and clean - // the corresponding field to avoid memory leaks. + /* + * Read the current continuation of the suspended `hasNext()` call and clean the corresponding field to avoid memory leaks. + * While this nulling out is unnecessary, it eliminates memory leaks (through the continuation) + * if the channel iterator accidentally remains GC-reachable after the channel is closed. + */ val cont = this.continuation!! this.continuation = null // Update the `hasNext()` internal result and inform From e1a0b5dc52116a7e6f5b1afe0cf24e1ef6a6e97d Mon Sep 17 00:00:00 2001 From: mvicsokolova <82594708+mvicsokolova@users.noreply.github.com> Date: Mon, 14 Aug 2023 18:54:07 +0200 Subject: [PATCH 12/53] Update atomicfu to 0.22.0 (#3848) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 914291afcf..e3acfdc268 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ kotlin_version=1.9.0 # Dependencies junit_version=4.12 junit5_version=5.7.0 -atomicfu_version=0.21.0 +atomicfu_version=0.22.0 knit_version=0.4.0 html_version=0.7.2 lincheck_version=2.18.1 From b9ff21839023e51886103bdcad08fde50ea6a0aa Mon Sep 17 00:00:00 2001 From: Nikita Bobko <20517828+nikitabobko@users.noreply.github.com> Date: Thu, 17 Aug 2023 10:03:58 +0200 Subject: [PATCH 13/53] Fix NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS compilation error #2 (#3851) The compilation error appeared after KT-59665 --- kotlinx-coroutines-core/js/test/TestBase.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/kotlinx-coroutines-core/js/test/TestBase.kt b/kotlinx-coroutines-core/js/test/TestBase.kt index f0e3a2dc7a..6979b677c8 100644 --- a/kotlinx-coroutines-core/js/test/TestBase.kt +++ b/kotlinx-coroutines-core/js/test/TestBase.kt @@ -15,6 +15,7 @@ public actual typealias TestResult = Promise public actual val isNative = false +@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual open class TestBase actual constructor() { public actual val isBoundByJsTestTimeout = true private var actionIndex = 0 From 5ac647263e7de671087abd2d34770046dbfbb962 Mon Sep 17 00:00:00 2001 From: Margarita Bobova Date: Fri, 18 Aug 2023 14:09:51 +0200 Subject: [PATCH 14/53] Fix NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS compilation error The compilation error appeared after KT-59665 --- kotlinx-coroutines-core/native/test/TestBase.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/kotlinx-coroutines-core/native/test/TestBase.kt b/kotlinx-coroutines-core/native/test/TestBase.kt index d7dfeeaeba..1fe5625622 100644 --- a/kotlinx-coroutines-core/native/test/TestBase.kt +++ b/kotlinx-coroutines-core/native/test/TestBase.kt @@ -15,6 +15,7 @@ public actual val isNative = true @Suppress("ACTUAL_WITHOUT_EXPECT") public actual typealias TestResult = Unit +@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual open class TestBase actual constructor() { public actual val isBoundByJsTestTimeout = false private var actionIndex = atomic(0) From aa6b74e373050e97a88f6049611c87fb9c16341a Mon Sep 17 00:00:00 2001 From: Marcus Cvjeticanin Date: Mon, 28 Aug 2023 18:00:07 +0200 Subject: [PATCH 15/53] Removing unused imports in build.gradle (#3860) --- build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle b/build.gradle index 0e7ee1d471..2b00f602e3 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,6 @@ import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension -import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin -import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.dokka.gradle.DokkaTaskPartial From 2d64ba1fc80126b40bf2579d989f0a113798e838 Mon Sep 17 00:00:00 2001 From: Margarita Bobova <32216159+woainikk@users.noreply.github.com> Date: Thu, 31 Aug 2023 11:23:56 +0200 Subject: [PATCH 16/53] Kotlin 1.9.20 migration: Fix NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS compilation error (#3870) The compilation error appeared after KT-59665 --- kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt b/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt index 6690972dfa..fb97a1f14f 100644 --- a/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt +++ b/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt @@ -25,6 +25,7 @@ private object BlackHole { var sink = 1 } +@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") internal actual typealias SuppressSupportingThrowable = SuppressSupportingThrowableImpl actual val Throwable.suppressed: Array From 4673870acf38ddfc12c1180096ffa8c3cf6652b4 Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Thu, 31 Aug 2023 16:58:16 +0200 Subject: [PATCH 17/53] Mute "expect/actual classes are experimental" warning This warning is introduced in Kotlin 1.9. KT-61573 If I don't mute this warning then the build fails because of `-Werror` --- .../kotlin/configure-compilation-conventions.gradle.kts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts b/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts index a891cf0ec2..68cbf74288 100644 --- a/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts @@ -19,8 +19,11 @@ configure(subprojects) { } val newOptions = listOf( - "-progressive", "-Xno-param-assertions", "-Xno-receiver-assertions", - "-Xno-call-assertions" + "-progressive", + "-Xno-param-assertions", + "-Xno-receiver-assertions", + "-Xexpect-actual-classes", + "-Xno-call-assertions", ) + optInAnnotations.map { "-opt-in=$it" } freeCompilerArgs = freeCompilerArgs + newOptions } From 9d1c62afebeae7588d6ba6fe720ae469977fac1b Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 1 Sep 2023 17:28:31 +0200 Subject: [PATCH 18/53] =?UTF-8?q?Get=20rid=20of=20ACTUAL=5FFUNCTION=5FWITH?= =?UTF-8?q?=5FDEFAULT=5FARGUMENTS=20to=20reduce=20the=20tensi=E2=80=A6=20(?= =?UTF-8?q?#3869)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Get rid of ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS to reduce the tension between develop and K2 branches * Remove obsolete workaround Target ticket: KT-61577 --- kotlinx-coroutines-core/common/test/AsyncTest.kt | 1 - kotlinx-coroutines-core/js/test/TestBase.kt | 4 +--- kotlinx-coroutines-core/jvm/test/TestBase.kt | 7 ++----- kotlinx-coroutines-core/native/test/TestBase.kt | 4 +--- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/kotlinx-coroutines-core/common/test/AsyncTest.kt b/kotlinx-coroutines-core/common/test/AsyncTest.kt index 2096a4d69e..3fff252318 100644 --- a/kotlinx-coroutines-core/common/test/AsyncTest.kt +++ b/kotlinx-coroutines-core/common/test/AsyncTest.kt @@ -8,7 +8,6 @@ package kotlinx.coroutines import kotlin.test.* -@Suppress("DEPRECATION") // cancel(cause) class AsyncTest : TestBase() { @Test diff --git a/kotlinx-coroutines-core/js/test/TestBase.kt b/kotlinx-coroutines-core/js/test/TestBase.kt index 6979b677c8..d50d93da9e 100644 --- a/kotlinx-coroutines-core/js/test/TestBase.kt +++ b/kotlinx-coroutines-core/js/test/TestBase.kt @@ -15,7 +15,6 @@ public actual typealias TestResult = Promise public actual val isNative = false -@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual open class TestBase actual constructor() { public actual val isBoundByJsTestTimeout = true private var actionIndex = 0 @@ -27,8 +26,7 @@ public actual open class TestBase actual constructor() { * Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not * complete successfully even if this exception is consumed somewhere in the test. */ - @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - public actual fun error(message: Any, cause: Throwable? = null): Nothing { + public actual fun error(message: Any, cause: Throwable?): Nothing { if (cause != null) console.log(cause) val exception = IllegalStateException( if (cause == null) message.toString() else "$message; caused by $cause") diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt index 3098138663..af1e2ac1ab 100644 --- a/kotlinx-coroutines-core/jvm/test/TestBase.kt +++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt @@ -55,8 +55,6 @@ public actual typealias TestResult = Unit * ``` */ @Suppress( - // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - "NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS", // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER" ) @@ -81,13 +79,12 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { */ private lateinit var previousOut: PrintStream - /** + /** * Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not * complete successfully even if this exception is consumed somewhere in the test. */ // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - public actual fun error(message: Any, cause: Throwable? = null): Nothing { + public actual fun error(message: Any, cause: Throwable?): Nothing { throw makeError(message, cause) } diff --git a/kotlinx-coroutines-core/native/test/TestBase.kt b/kotlinx-coroutines-core/native/test/TestBase.kt index 1fe5625622..173bcc30ae 100644 --- a/kotlinx-coroutines-core/native/test/TestBase.kt +++ b/kotlinx-coroutines-core/native/test/TestBase.kt @@ -15,7 +15,6 @@ public actual val isNative = true @Suppress("ACTUAL_WITHOUT_EXPECT") public actual typealias TestResult = Unit -@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual open class TestBase actual constructor() { public actual val isBoundByJsTestTimeout = false private var actionIndex = atomic(0) @@ -26,8 +25,7 @@ public actual open class TestBase actual constructor() { * Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not * complete successfully even if this exception is consumed somewhere in the test. */ - @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") - public actual fun error(message: Any, cause: Throwable? = null): Nothing { + public actual fun error(message: Any, cause: Throwable?): Nothing { val exception = IllegalStateException(message.toString(), cause) if (error == null) error = exception throw exception From 454acd88e90bb0904bef1e38fc7e75babbbaa14e Mon Sep 17 00:00:00 2001 From: Margarita Bobova Date: Thu, 31 Aug 2023 11:23:56 +0200 Subject: [PATCH 19/53] Kotlin 1.9.20 migration: Fix ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER compilation error (#3870) The compilation error appeared after KT-22841 --- .../native/test/ConcurrentTestUtilities.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt b/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt index fb97a1f14f..b7040d68ce 100644 --- a/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt +++ b/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt @@ -25,7 +25,10 @@ private object BlackHole { var sink = 1 } -@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") +@Suppress( + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER", +) internal actual typealias SuppressSupportingThrowable = SuppressSupportingThrowableImpl actual val Throwable.suppressed: Array From fd5a58bd1ccc5727d0ce423aa01d1be78b993bef Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Thu, 14 Sep 2023 12:14:05 +0300 Subject: [PATCH 20/53] Document that SharedFlow.collect subscribes by its first suspension (#3885) --- .../common/src/flow/SharedFlow.kt | 10 ++++++++++ .../common/test/flow/sharing/ShareInTest.kt | 5 +++++ .../test/flow/sharing/SharedFlowTest.kt | 20 +++++++++++++++++++ .../common/test/flow/sharing/StateFlowTest.kt | 5 +++++ .../common/test/flow/sharing/StateInTest.kt | 5 +++++ 5 files changed, 45 insertions(+) diff --git a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt index b4833fead6..1ded0a1750 100644 --- a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt +++ b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt @@ -137,6 +137,16 @@ public interface SharedFlow : Flow { * **A shared flow never completes**. A call to [Flow.collect] or any other terminal operator * on a shared flow never completes normally. * + * It is guaranteed that, by the time the first suspension happens, [collect] has already subscribed to the + * [SharedFlow] and is eligible for receiving emissions. In particular, the following code will always print `1`: + * ``` + * val flow = MutableSharedFlow() + * launch(start = CoroutineStart.UNDISPATCHED) { + * flow.collect { println(1) } + * } + * flow.emit(1) + * ``` + * * @see [Flow.collect] for implementation and inheritance details. */ override suspend fun collect(collector: FlowCollector): Nothing diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt index cf83a50b0f..19b70032f3 100644 --- a/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt +++ b/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt @@ -236,4 +236,9 @@ class ShareInTest : TestBase() { assertEquals(239, shared.first()) j.cancel() } + + @Test + fun testSubscriptionByFirstSuspensionInSharedFlow() = runTest { + testSubscriptionByFirstSuspensionInCollect(flowOf(1).stateIn(this@runTest), emit = { }) + } } diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt index 98e04f00e8..bb36c0ef9a 100644 --- a/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt +++ b/kotlinx-coroutines-core/common/test/flow/sharing/SharedFlowTest.kt @@ -818,4 +818,24 @@ class SharedFlowTest : TestBase() { j2.cancelAndJoin() assertEquals(0, flow.subscriptionCount.first()) } + + @Test + fun testSubscriptionByFirstSuspensionInSharedFlow() = runTest { + testSubscriptionByFirstSuspensionInCollect(MutableSharedFlow()) { emit(it) } + } +} + +/** + * Check that, by the time [SharedFlow.collect] suspends for the first time, its subscription is already active. + */ +inline fun> CoroutineScope.testSubscriptionByFirstSuspensionInCollect(flow: T, emit: T.(Int) -> Unit) { + var received = 0 + val job = launch(start = CoroutineStart.UNDISPATCHED) { + flow.collect { + received = it + } + } + flow.emit(1) + assertEquals(1, received) + job.cancel() } diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/StateFlowTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/StateFlowTest.kt index be4f8c536b..a77f091b4f 100644 --- a/kotlinx-coroutines-core/common/test/flow/sharing/StateFlowTest.kt +++ b/kotlinx-coroutines-core/common/test/flow/sharing/StateFlowTest.kt @@ -181,4 +181,9 @@ class StateFlowTest : TestBase() { state.update { it + 3 } assertEquals(5, state.value) } + + @Test + fun testSubscriptionByFirstSuspensionInStateFlow() = runTest { + testSubscriptionByFirstSuspensionInCollect(MutableStateFlow(0)) { value = it; yield() } + } } diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/StateInTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/StateInTest.kt index d0e76c461e..a27489bb4a 100644 --- a/kotlinx-coroutines-core/common/test/flow/sharing/StateInTest.kt +++ b/kotlinx-coroutines-core/common/test/flow/sharing/StateInTest.kt @@ -86,4 +86,9 @@ class StateInTest : TestBase() { assertFailsWith { flow.stateIn(CoroutineScope(currentCoroutineContext() + Job() + ceh)) } finish(3) } + + @Test + fun testSubscriptionByFirstSuspensionInStateFlow() = runTest { + testSubscriptionByFirstSuspensionInCollect(flowOf(1).stateIn(this@runTest)) { } + } } From 84a2cbaa416a1ee32adf4b09d501850e5994bac1 Mon Sep 17 00:00:00 2001 From: Dmitry Savvinov Date: Tue, 19 Sep 2023 16:52:17 +0200 Subject: [PATCH 21/53] Minor: add forgotten Suppress for ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER (#3891) --- .../concurrent/src/internal/LockFreeLinkedList.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt index a692e26f7c..881c22efa0 100644 --- a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt +++ b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt @@ -372,6 +372,9 @@ public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() { // optimization: because head is never removed, we don't have to read _next.value to check these: override val isRemoved: Boolean get() = false + + // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") override fun nextIfRemoved(): Node? = null // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 From 7659d65a6aa631a15bf0a3c6b079245d3ec016c1 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 20 Sep 2023 17:52:55 +0200 Subject: [PATCH 22/53] Introduce workaround (in fact, fix the previously incorrect expect-actual mapping) for CancellationException. (#3850) See KT-61168 & KT-22004 for the future solution --- kotlinx-coroutines-core/common/src/Exceptions.common.kt | 1 - kotlinx-coroutines-core/js/src/Exceptions.kt | 5 +++++ kotlinx-coroutines-core/jvm/src/Exceptions.kt | 1 - kotlinx-coroutines-core/native/src/Exceptions.kt | 5 +++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/Exceptions.common.kt b/kotlinx-coroutines-core/common/src/Exceptions.common.kt index 6d5442dfdc..27d11353e7 100644 --- a/kotlinx-coroutines-core/common/src/Exceptions.common.kt +++ b/kotlinx-coroutines-core/common/src/Exceptions.common.kt @@ -14,7 +14,6 @@ public class CompletionHandlerException(message: String, cause: Throwable) : Run public expect open class CancellationException(message: String?) : IllegalStateException -@Suppress("FunctionName", "NO_ACTUAL_FOR_EXPECT") public expect fun CancellationException(message: String?, cause: Throwable?) : CancellationException internal expect class JobCancellationException( diff --git a/kotlinx-coroutines-core/js/src/Exceptions.kt b/kotlinx-coroutines-core/js/src/Exceptions.kt index da9979b603..2295f93709 100644 --- a/kotlinx-coroutines-core/js/src/Exceptions.kt +++ b/kotlinx-coroutines-core/js/src/Exceptions.kt @@ -12,6 +12,11 @@ package kotlinx.coroutines */ public actual typealias CancellationException = kotlin.coroutines.cancellation.CancellationException +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +@kotlin.internal.LowPriorityInOverloadResolution +public actual fun CancellationException(message: String?, cause: Throwable?): CancellationException = + CancellationException(message, 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] diff --git a/kotlinx-coroutines-core/jvm/src/Exceptions.kt b/kotlinx-coroutines-core/jvm/src/Exceptions.kt index 48b4788cc5..9c9f5bf441 100644 --- a/kotlinx-coroutines-core/jvm/src/Exceptions.kt +++ b/kotlinx-coroutines-core/jvm/src/Exceptions.kt @@ -17,7 +17,6 @@ public actual typealias CancellationException = java.util.concurrent.Cancellatio /** * 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) } diff --git a/kotlinx-coroutines-core/native/src/Exceptions.kt b/kotlinx-coroutines-core/native/src/Exceptions.kt index 1a923c40ff..f9d0f5db73 100644 --- a/kotlinx-coroutines-core/native/src/Exceptions.kt +++ b/kotlinx-coroutines-core/native/src/Exceptions.kt @@ -15,6 +15,11 @@ import kotlinx.coroutines.internal.SuppressSupportingThrowableImpl */ public actual typealias CancellationException = kotlin.coroutines.cancellation.CancellationException +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +@kotlin.internal.LowPriorityInOverloadResolution +public actual fun CancellationException(message: String?, cause: Throwable?): CancellationException = + CancellationException(message, 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] From 64d8874911f69761dc15cdf8106699f4904aada2 Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Thu, 5 Oct 2023 16:40:22 +0200 Subject: [PATCH 23/53] "member scope mismatch for open expect" was downgraded from error to warning KT-62263 --- .../concurrent/src/CompletionHandler.kt | 4 ++-- .../src/internal/LockFreeLinkedList.kt | 22 +++++++++---------- .../js/src/internal/LinkedList.kt | 4 ++-- kotlinx-coroutines-core/jvm/src/EventLoop.kt | 6 ++--- kotlinx-coroutines-core/jvm/src/Executors.kt | 4 ++-- .../jvm/src/SchedulerTask.kt | 4 ++-- kotlinx-coroutines-core/jvm/test/TestBase.kt | 20 ++++++++--------- .../native/src/internal/Synchronized.kt | 2 +- .../native/test/ConcurrentTestUtilities.kt | 2 +- 9 files changed, 34 insertions(+), 34 deletions(-) diff --git a/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt b/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt index 6751791942..66d65f9e2d 100644 --- a/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt +++ b/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.internal.* // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 // New 'CompletionHandler` supertype is added compared to the expect declaration. // Probably we can add it to JS and common too, to avoid the suppression/opt-in -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER") +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") internal actual abstract class CompletionHandlerBase actual constructor() : LockFreeLinkedListNode(), CompletionHandler { actual abstract override fun invoke(cause: Throwable?) } @@ -19,7 +19,7 @@ internal actual inline val CompletionHandlerBase.asHandler: CompletionHandler ge // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 // New 'CompletionHandler` supertype is added compared to the expect declaration. // Probably we can add it to JS and common too, to avoid the suppression/opt-in -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER") +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") internal actual abstract class CancelHandlerBase actual constructor() : CompletionHandler { actual abstract override fun invoke(cause: Throwable?) } diff --git a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt index 881c22efa0..cbc83a6400 100644 --- a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt +++ b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt @@ -45,7 +45,7 @@ internal val CONDITION_FALSE: Any = Symbol("CONDITION_FALSE") @Suppress( "LeakingThis", // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER" + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING" ) @InternalCoroutinesApi public actual open class LockFreeLinkedListNode { @@ -73,7 +73,7 @@ public actual open class LockFreeLinkedListNode { } // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") @PublishedApi internal inline fun makeCondAddOp(node: Node, crossinline condition: () -> Boolean): CondAddOp = object : CondAddOp(node) { @@ -81,11 +81,11 @@ public actual open class LockFreeLinkedListNode { } // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("MODALITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION") + @Suppress("MODALITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION_WARNING") public actual open val isRemoved: Boolean get() = next is Removed // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") // LINEARIZABLE. Returns Node | Removed public val next: Any get() { _next.loop { next -> @@ -177,7 +177,7 @@ public actual open class LockFreeLinkedListNode { * Returns `false` if `next` was not following `this` node. */ // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") @PublishedApi internal fun addNext(node: Node, next: Node): Boolean { node._prev.lazySet(this) @@ -189,7 +189,7 @@ public actual open class LockFreeLinkedListNode { } // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") // returns UNDECIDED, SUCCESS or FAILURE @PublishedApi internal fun tryCondAddNext(node: Node, next: Node, condAdd: CondAddOp): Int { @@ -214,7 +214,7 @@ public actual open class LockFreeLinkedListNode { removeOrNext() == null // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") // returns null if removed successfully or next node if this node is already removed @PublishedApi internal fun removeOrNext(): Node? { @@ -272,7 +272,7 @@ public actual open class LockFreeLinkedListNode { } // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") protected open fun nextIfRemoved(): Node? = (next as? Removed)?.ref /** @@ -330,7 +330,7 @@ public actual open class LockFreeLinkedListNode { } // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") internal fun validateNode(prev: Node, next: Node) { assert { prev === this._prev.value } assert { next === this._next.value } @@ -352,7 +352,7 @@ internal fun Any.unwrap(): Node = (this as? Removed)?.ref ?: this as Node * @suppress **This is unstable API and it is subject to change.** */ // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER") +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() { public actual val isEmpty: Boolean get() = next === this @@ -378,7 +378,7 @@ public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() { override fun nextIfRemoved(): Node? = null // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") internal fun validate() { var prev: Node = this var cur: Node = next as Node diff --git a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt index 1a1c346959..aa46e5a814 100644 --- a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt +++ b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt @@ -14,9 +14,9 @@ private typealias Node = LinkedListNode // :TODO: Remove when fixed: https://youtrack.jetbrains.com/issue/KT-23703 "NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS", // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER", + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING", // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER" + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING" ) public actual typealias LockFreeLinkedListNode = LinkedListNode diff --git a/kotlinx-coroutines-core/jvm/src/EventLoop.kt b/kotlinx-coroutines-core/jvm/src/EventLoop.kt index 7acefd8c89..5a8a572789 100644 --- a/kotlinx-coroutines-core/jvm/src/EventLoop.kt +++ b/kotlinx-coroutines-core/jvm/src/EventLoop.kt @@ -9,10 +9,10 @@ import kotlinx.coroutines.scheduling.* import kotlinx.coroutines.scheduling.CoroutineScheduler // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER") +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") internal actual abstract class EventLoopImplPlatform: EventLoop() { // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") protected abstract val thread: Thread protected actual fun unpark() { @@ -22,7 +22,7 @@ internal actual abstract class EventLoopImplPlatform: EventLoop() { } // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("MODALITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION") + @Suppress("MODALITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION_WARNING") protected actual open fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask) { DefaultExecutor.schedule(now, delayedTask) } diff --git a/kotlinx-coroutines-core/jvm/src/Executors.kt b/kotlinx-coroutines-core/jvm/src/Executors.kt index 0449c338fd..c236d637c4 100644 --- a/kotlinx-coroutines-core/jvm/src/Executors.kt +++ b/kotlinx-coroutines-core/jvm/src/Executors.kt @@ -39,9 +39,9 @@ public abstract class ExecutorCoroutineDispatcher: CoroutineDispatcher(), Closea @Suppress( // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER", + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING", // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER" + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING" ) @ExperimentalCoroutinesApi public actual typealias CloseableCoroutineDispatcher = ExecutorCoroutineDispatcher diff --git a/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt b/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt index a946f8173d..bf5e066984 100644 --- a/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt +++ b/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt @@ -7,11 +7,11 @@ package kotlinx.coroutines import kotlinx.coroutines.scheduling.* // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER") +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") internal actual typealias SchedulerTask = Task // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER") +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") internal actual typealias SchedulerTaskContext = TaskContext @Suppress("EXTENSION_SHADOWED_BY_MEMBER") diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt index af1e2ac1ab..8153a97667 100644 --- a/kotlinx-coroutines-core/jvm/test/TestBase.kt +++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt @@ -56,7 +56,7 @@ public actual typealias TestResult = Unit */ @Suppress( // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER" + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING" ) public actual open class TestBase(private var disableOutCheck: Boolean) { @@ -89,7 +89,7 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { } // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") public fun hasError() = error.get() != null private fun makeError(message: Any, cause: Throwable? = null): IllegalStateException = @@ -114,7 +114,7 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { * test will not complete successfully even if this exception is consumed somewhere in the test. */ // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") public inline fun check(value: Boolean, lazyMessage: () -> Any) { if (!value) error(lazyMessage()) } @@ -163,7 +163,7 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { }) // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") fun println(message: Any?) { if (disableOutCheck) kotlin.io.println(message) else previousOut.println(message) @@ -171,7 +171,7 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { @Before // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") fun before() { initPoolsBeforeTest() threadsBefore = currentThreads() @@ -188,7 +188,7 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { } // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") @After fun onCompletion() { // onCompletion should not throw exceptions before it finishes all cleanup, so that other tests always @@ -217,13 +217,13 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { } // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") fun initPoolsBeforeTest() { DefaultScheduler.usePrivateScheduler() } // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") fun shutdownPoolsAfterTest() { DefaultScheduler.shutdown(SHUTDOWN_TIMEOUT) DefaultExecutor.shutdownForTests(SHUTDOWN_TIMEOUT) @@ -266,7 +266,7 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { } // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") protected inline fun assertFailsWith(block: () -> Unit): T { val result = runCatching(block) assertTrue(result.exceptionOrNull() is T, "Expected ${T::class}, but had $result") @@ -274,7 +274,7 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { } // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") + @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") protected suspend fun currentDispatcher() = coroutineContext[ContinuationInterceptor]!! } diff --git a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt index 7adcec12ba..c3162497f4 100644 --- a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt +++ b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt @@ -12,7 +12,7 @@ import kotlinx.atomicfu.locks.withLock as withLock2 */ @InternalCoroutinesApi // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER") +@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") public actual typealias SynchronizedObject = kotlinx.atomicfu.locks.SynchronizedObject /** diff --git a/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt b/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt index b7040d68ce..b14f035b7e 100644 --- a/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt +++ b/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt @@ -27,7 +27,7 @@ private object BlackHole { @Suppress( // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER", + "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING", ) internal actual typealias SuppressSupportingThrowable = SuppressSupportingThrowableImpl From 1fc8c3797aeea37f054969550d8860161908636a Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 6 Oct 2023 14:25:25 +0200 Subject: [PATCH 24/53] Add java.base/sun.security.action required for Lincheck to run on Java 17 (#3908) --- kotlinx-coroutines-core/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index b21f1cbfd5..86350ce546 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -323,7 +323,9 @@ task jvmLincheckTestAdditional(type: Test, dependsOn: compileTestKotlinJvm) { static void configureJvmForLincheck(task, additional = false) { task.minHeapSize = '1g' task.maxHeapSize = '4g' // we may need more space for building an interleaving tree in the model checking mode + // https://github.com/JetBrains/lincheck#java-9 task.jvmArgs = ['--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED', // required for transformation + '--add-exports', 'java.base/sun.security.action=ALL-UNNAMED', '--add-exports', 'java.base/jdk.internal.util=ALL-UNNAMED'] // in the model checking mode // Adjust internal algorithmic parameters to increase the testing quality instead of performance. var segmentSize = additional ? '2' : '1' From 44d46ea0dd651fa2359ca3066c5e7fd79d926b91 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Mon, 23 Oct 2023 12:24:19 +0300 Subject: [PATCH 25/53] Ensure `runTest` unsubscribes from the exception handler (#3909) Fixes #3897 --- .../common/src/TestScope.kt | 1 + .../common/test/Helpers.kt | 10 +-- .../common/test/RunTestTest.kt | 65 +++++++++++++++++++ .../common/test/TestScopeTest.kt | 4 +- .../jvm/test/UncaughtExceptionsTest.kt | 50 ++++++++++++++ 5 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 kotlinx-coroutines-test/jvm/test/UncaughtExceptionsTest.kt diff --git a/kotlinx-coroutines-test/common/src/TestScope.kt b/kotlinx-coroutines-test/common/src/TestScope.kt index fa6e3930d8..ae644ae804 100644 --- a/kotlinx-coroutines-test/common/src/TestScope.kt +++ b/kotlinx-coroutines-test/common/src/TestScope.kt @@ -239,6 +239,7 @@ internal class TestScopeImpl(context: CoroutineContext) : uncaughtExceptions } if (exceptions.isNotEmpty()) { + ExceptionCollector.removeOnExceptionCallback(lock) throw UncaughtExceptionsBeforeTest().apply { for (e in exceptions) addSuppressed(e) diff --git a/kotlinx-coroutines-test/common/test/Helpers.kt b/kotlinx-coroutines-test/common/test/Helpers.kt index 345c66f91a..db60c5ddf6 100644 --- a/kotlinx-coroutines-test/common/test/Helpers.kt +++ b/kotlinx-coroutines-test/common/test/Helpers.kt @@ -47,14 +47,16 @@ fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): TestResu */ expect fun testResultChain(block: () -> TestResult, after: (Result) -> TestResult): TestResult -fun testResultChain(vararg chained: (Result) -> TestResult): TestResult = +fun testResultChain(vararg chained: (Result) -> TestResult, initialResult: Result = Result.success(Unit)): TestResult = if (chained.isEmpty()) { - createTestResult { } + createTestResult { + initialResult.getOrThrow() + } } else { testResultChain(block = { - chained[0](Result.success(Unit)) + chained[0](initialResult) }) { - testResultChain(*chained.drop(1).toTypedArray()) + testResultChain(*chained.drop(1).toTypedArray(), initialResult = it) } } diff --git a/kotlinx-coroutines-test/common/test/RunTestTest.kt b/kotlinx-coroutines-test/common/test/RunTestTest.kt index da2bdcfc76..f4ad53abf1 100644 --- a/kotlinx-coroutines-test/common/test/RunTestTest.kt +++ b/kotlinx-coroutines-test/common/test/RunTestTest.kt @@ -408,4 +408,69 @@ class RunTestTest { fun testCoroutineCompletingWithoutDispatch() = runTest(timeout = Duration.INFINITE) { launch(Dispatchers.Default) { delay(100) } } + + /** + * Tests that [runTest] cleans up the exception handler even if it threw on initialization. + * + * This test must be run manually, because it writes garbage to the log. + * + * The JVM-only source set contains a test equivalent to this one that isn't ignored. + */ + @Test + @Ignore + fun testExceptionCaptorCleanedUpOnPreliminaryExit(): TestResult = testResultChain({ + // step 1: installing the exception handler + println("step 1") + runTest { } + }, { + it.getOrThrow() + // step 2: throwing an uncaught exception to be caught by the exception-handling system + println("step 2") + createTestResult { + launch(NonCancellable) { throw TestException("A") } + } + }, { + it.getOrThrow() + // step 3: trying to run a test should immediately fail, even before entering the test body + println("step 3") + try { + runTest { + fail("unreached") + } + fail("unreached") + } catch (e: UncaughtExceptionsBeforeTest) { + val cause = e.suppressedExceptions.single() + assertIs(cause) + assertEquals("A", cause.message) + } + // step 4: trying to run a test again should not fail with an exception + println("step 4") + runTest { + } + }, { + it.getOrThrow() + // step 5: throwing an uncaught exception to be caught by the exception-handling system, again + println("step 5") + createTestResult { + launch(NonCancellable) { throw TestException("B") } + } + }, { + it.getOrThrow() + // step 6: trying to run a test should immediately fail, again + println("step 6") + try { + runTest { + fail("unreached") + } + fail("unreached") + } catch (e: Exception) { + val cause = e.suppressedExceptions.single() + assertIs(cause) + assertEquals("B", cause.message) + } + // step 7: trying to run a test again should not fail with an exception, again + println("step 7") + runTest { + } + }) } diff --git a/kotlinx-coroutines-test/common/test/TestScopeTest.kt b/kotlinx-coroutines-test/common/test/TestScopeTest.kt index 433faef7ac..76791365fc 100644 --- a/kotlinx-coroutines-test/common/test/TestScopeTest.kt +++ b/kotlinx-coroutines-test/common/test/TestScopeTest.kt @@ -495,9 +495,11 @@ class TestScopeTest { * Tests that the [TestScope] exception reporting mechanism will report the exceptions that happen between * different tests. * - * This test must be ran manually, because such exceptions still go through the global exception handler + * This test must be run manually, because such exceptions still go through the global exception handler * (as there's no guarantee that another test will happen), and the global exception handler will * log the exceptions or, on Native, crash the test suite. + * + * The JVM-only source set contains a test equivalent to this one that isn't ignored. */ @Test @Ignore diff --git a/kotlinx-coroutines-test/jvm/test/UncaughtExceptionsTest.kt b/kotlinx-coroutines-test/jvm/test/UncaughtExceptionsTest.kt new file mode 100644 index 0000000000..d3802eda14 --- /dev/null +++ b/kotlinx-coroutines-test/jvm/test/UncaughtExceptionsTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package kotlinx.coroutines.test + +import org.junit.Test +import kotlin.test.* + +/** + * Tests that check the behavior of the test framework when there are stray uncaught exceptions. + * These tests are JVM-only because only the JVM allows to set a global uncaught exception handler and validate the + * behavior without checking the test logs. + * Nevertheless, each test here has a corresponding test in the common source set that can be run manually. + */ +class UncaughtExceptionsTest { + + val oldExceptionHandler = Thread.getDefaultUncaughtExceptionHandler() + val uncaughtExceptions = mutableListOf() + + @BeforeTest + fun setUp() { + Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> + uncaughtExceptions.add(throwable) + } + } + + @AfterTest + fun tearDown() { + Thread.setDefaultUncaughtExceptionHandler(oldExceptionHandler) + } + + @Test + fun testReportingStrayUncaughtExceptionsBetweenTests() { + TestScopeTest().testReportingStrayUncaughtExceptionsBetweenTests() + assertEquals(1, uncaughtExceptions.size, "Expected 1 uncaught exception, but got $uncaughtExceptions") + val exception = assertIs(uncaughtExceptions.single()) + assertEquals("x", exception.message) + } + + @Test + fun testExceptionCaptorCleanedUpOnPreliminaryExit() { + RunTestTest().testExceptionCaptorCleanedUpOnPreliminaryExit() + assertEquals(2, uncaughtExceptions.size, "Expected 2 uncaught exceptions, but got $uncaughtExceptions") + for (exception in uncaughtExceptions) { + assertIs(exception) + } + assertEquals("A", uncaughtExceptions[0].message) + assertEquals("B", uncaughtExceptions[1].message) + } +} From 2b5d93f2dbc0de0f82e4ec9ab7753e5f24da6e30 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 23 Oct 2023 12:09:28 +0200 Subject: [PATCH 26/53] Introduce the notion of @BeningDataRace (#3873) * Mark BufferedChannelIterator.continuation as @BeningDataRace to address potential UB and OoTA on K/N * Explain benign data-race on SelectImplementation.clauses and mark it with @BenignDataRace * Explain benign data-race on SelectImplementation.internalResult and mark it with @BenignDataRace Fixes #3834 Fixes #3843 --- .../common/src/channels/BufferedChannel.kt | 5 +++++ .../common/src/internal/Concurrent.common.kt | 12 ++++++++++++ .../common/src/selects/Select.kt | 16 +++++++++++++--- .../native/src/internal/Concurrent.kt | 2 ++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt index 08cda7e9e8..9224ae8fce 100644 --- a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt @@ -1582,7 +1582,12 @@ internal open class BufferedChannel( * When [hasNext] suspends, this field stores the corresponding * continuation. The [tryResumeHasNext] and [tryResumeHasNextOnClosedChannel] * function resume this continuation when the [hasNext] invocation should complete. + * + * This property is the subject to bening data race: + * It is nulled-out on both completion and cancellation paths that + * could happen concurrently. */ + @BenignDataRace private var continuation: CancellableContinuationImpl? = null // `hasNext()` is just a special receive operation. diff --git a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt index 848a42c867..c06fcaf7b2 100644 --- a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt +++ b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt @@ -12,3 +12,15 @@ internal expect class ReentrantLock() { internal expect inline fun ReentrantLock.withLock(action: () -> T): T internal expect fun identitySet(expectedSize: Int): MutableSet + +/** + * Annotation indicating that the marked property is the subject of benign data race. + * LLVM does not support this notion, so on K/N platforms we alias it into `@Volatile` to prevent potential OoTA. + * + * The purpose of this annotation is not to save an extra-volatile on JVM platform, but rather to explicitly emphasize + * that data-race is benign. + */ +@OptionalExpectation +@Target(AnnotationTarget.FIELD) +@OptIn(ExperimentalMultiplatform::class) +internal expect annotation class BenignDataRace() diff --git a/kotlinx-coroutines-core/common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt index 3ac3cb6f27..79acdbd1c2 100644 --- a/kotlinx-coroutines-core/common/src/selects/Select.kt +++ b/kotlinx-coroutines-core/common/src/selects/Select.kt @@ -372,7 +372,12 @@ internal open class SelectImplementation( /** * List of clauses waiting on this `select` instance. + * + * This property is the subject to bening data race: concurrent cancellation might null-out this property + * while [trySelect] operation reads it and iterates over its content. + * A logical race is resolved by the consensus on [state] property. */ + @BenignDataRace private var clauses: MutableList? = ArrayList(2) /** @@ -407,7 +412,13 @@ internal open class SelectImplementation( * one that stores either result when the clause is successfully registered ([inRegistrationPhase] is `true`), * or [DisposableHandle] instance when the clause is completed during registration ([inRegistrationPhase] is `false`). * Yet, this optimization is omitted for code simplicity. + * + * This property is the subject to benign data race: + * [Cleanup][cleanup] procedure can be invoked both as part of the completion sequence + * and as a cancellation handler triggered by an external cancellation. + * In both scenarios, [NO_RESULT] is written to this property via race. */ + @BenignDataRace private var internalResult: Any? = NO_RESULT /** @@ -621,9 +632,8 @@ internal open class SelectImplementation( // try to resume the continuation. this.internalResult = internalResult if (cont.tryResume(onCancellation)) return TRY_SELECT_SUCCESSFUL - // If the resumption failed, we need to clean - // the [result] field to avoid memory leaks. - this.internalResult = null + // If the resumption failed, we need to clean the [result] field to avoid memory leaks. + this.internalResult = NO_RESULT return TRY_SELECT_CANCELLED } } diff --git a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt index 17975e2e7f..f46326bcda 100644 --- a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt +++ b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt @@ -29,3 +29,5 @@ internal open class SuppressSupportingThrowableImpl : Throwable() { } } +@Suppress("ACTUAL_WITHOUT_EXPECT") // This suppress can be removed in 2.0: KT-59355 +internal actual typealias BenignDataRace = kotlin.concurrent.Volatile From 5a570e1fd1b2a27d2ff6fa1ad41d316d2167dfe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Thu, 2 Nov 2023 09:40:21 +0100 Subject: [PATCH 27/53] Work around KT-58685 (#3881) Implement `withLock` and `withPermit` without try/finally, as the idiomatic implementation leads to incorrect code being produced by the compiler. --- .../common/src/sync/Mutex.kt | 12 +++++++++--- .../common/src/sync/Semaphore.kt | 12 +++++++++--- .../common/test/sync/MutexTest.kt | 16 ++++++++++++++++ .../common/test/sync/SemaphoreTest.kt | 18 +++++++++++++++++- 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt index 1fc2b41194..b51b75f740 100644 --- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt +++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt @@ -122,12 +122,18 @@ public suspend inline fun Mutex.withLock(owner: Any? = null, action: () -> T callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + // Cannot use 'finally' in this function because of KT-58685 + // See kotlinx.coroutines.sync.MutexTest.testWithLockJsMiscompilation + lock(owner) - try { - return action() - } finally { + val result = try { + action() + } catch (e: Throwable) { unlock(owner) + throw e } + unlock(owner) + return result } diff --git a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt index 9f30721df5..f8a4791f27 100644 --- a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt +++ b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt @@ -83,12 +83,18 @@ public suspend inline fun Semaphore.withPermit(action: () -> T): T { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } + // Cannot use 'finally' in this function because of KT-58685 + // See kotlinx.coroutines.sync.SemaphoreTest.testWithPermitJsMiscompilation + acquire() - try { - return action() - } finally { + val result = try { + action() + } catch (e: Throwable) { release() + throw e } + release() + return result } @Suppress("UNCHECKED_CAST") diff --git a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt index b4acd94e9c..2f45bb50d5 100644 --- a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt +++ b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt @@ -148,4 +148,20 @@ class MutexTest : TestBase() { assertFailsWith { mutex.lock(owner) } assertFailsWith { select { mutex.onLock(owner) {} } } } + + @Test + fun testWithLockJsMiscompilation() = runTest { + // This is a reproducer for KT-58685 + // On Kotlin/JS IR, the compiler miscompiles calls to 'unlock' in an inlined finally + // This is visible on the withLock function + // Until the compiler bug is fixed, this test case checks that we do not suffer from it + val mutex = Mutex() + assertFailsWith { + try { + mutex.withLock { null } ?: throw IndexOutOfBoundsException() // should throw… + } catch (e: Exception) { + throw e // …but instead fails here + } + } + } } diff --git a/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt b/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt index b4ff88b895..9a1ed01a69 100644 --- a/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt +++ b/kotlinx-coroutines-core/common/test/sync/SemaphoreTest.kt @@ -168,4 +168,20 @@ class SemaphoreTest : TestBase() { assertFailsWith { Semaphore(1, -1) } assertFailsWith { Semaphore(1, 2) } } -} \ No newline at end of file + + @Test + fun testWithPermitJsMiscompilation() = runTest { + // This is a reproducer for KT-58685 + // On Kotlin/JS IR, the compiler miscompiles calls to 'release' in an inlined finally + // This is visible on the withPermit function + // Until the compiler bug is fixed, this test case checks that we do not suffer from it + val semaphore = Semaphore(1) + assertFailsWith { + try { + semaphore.withPermit { null } ?: throw IndexOutOfBoundsException() // should throw… + } catch (e: Exception) { + throw e // …but instead fails here + } + } + } +} From b2ef7aba6bfa810a5f49f37506ce7f6a888f4561 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 6 Nov 2023 11:38:16 +0100 Subject: [PATCH 28/53] Update Kotlin to 1.9.20 (#3925) --- README.md | 10 +++++----- gradle.properties | 2 +- integration-testing/gradle.properties | 2 +- .../api/kotlinx-coroutines-core.api | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e675caef6e..25df7aa8b5 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ [![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.2)](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.2) -[![Kotlin](https://img.shields.io/badge/kotlin-1.9.0-blue.svg?logo=kotlin)](http://kotlinlang.org) +[![Kotlin](https://img.shields.io/badge/kotlin-1.9.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/) Library support for Kotlin coroutines with [multiplatform](#multiplatform) support. -This is a companion version for the Kotlin `1.9.0` release. +This is a companion version for the Kotlin `1.9.20` release. ```kotlin suspend fun main() = coroutineScope { @@ -93,7 +93,7 @@ And make sure that you use the latest Kotlin version: ```xml - 1.9.0 + 1.9.20 ``` @@ -112,10 +112,10 @@ And make sure that you use the latest Kotlin version: ```kotlin plugins { // For build.gradle.kts (Kotlin DSL) - kotlin("jvm") version "1.9.0" + kotlin("jvm") version "1.9.20" // For build.gradle (Groovy DSL) - id "org.jetbrains.kotlin.jvm" version "1.9.0" + id "org.jetbrains.kotlin.jvm" version "1.9.20" } ``` diff --git a/gradle.properties b/gradle.properties index e3acfdc268..c8d0c19696 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ # Kotlin version=1.7.2-SNAPSHOT group=org.jetbrains.kotlinx -kotlin_version=1.9.0 +kotlin_version=1.9.20 # Dependencies junit_version=4.12 diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties index 59811645d7..b662e51e15 100644 --- a/integration-testing/gradle.properties +++ b/integration-testing/gradle.properties @@ -1,4 +1,4 @@ -kotlin_version=1.9.0 +kotlin_version=1.9.20 coroutines_version=1.7.2-SNAPSHOT asm_version=9.3 diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index de53f89a21..ad5e68cba3 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -321,7 +321,7 @@ public abstract interface annotation class kotlinx/coroutines/DelicateCoroutines } public final class kotlinx/coroutines/DispatchedCoroutine { - public static final fun get_decision$FU ()Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater; + public static final synthetic fun get_decision$volatile$FU$kotlinx_coroutines_core ()Ljava/util/concurrent/atomic/AtomicIntegerFieldUpdater; } public abstract class kotlinx/coroutines/DispatchedTask : kotlinx/coroutines/scheduling/Task { From e69bc2c64fd82503d33dbcf85d2bce22f4a34821 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 6 Nov 2023 16:19:19 +0100 Subject: [PATCH 29/53] Kotlin 1.9.20 update followup (#3926) * Do not conditionally enable K/N deprecated targets * Get rid of redundant suppresses that are not relevant to 1.9.20 Fixes #3828 Fixes #3846 --- gradle/compile-native-multiplatform.gradle | 11 -------- .../concurrent/src/CompletionHandler.kt | 8 ------ .../src/internal/LockFreeLinkedList.kt | 28 +------------------ .../js/src/internal/LinkedList.kt | 9 +----- kotlinx-coroutines-core/jvm/src/EventLoop.kt | 7 +---- kotlinx-coroutines-core/jvm/src/Executors.kt | 6 ---- .../jvm/src/SchedulerTask.kt | 4 --- kotlinx-coroutines-core/jvm/test/TestBase.kt | 26 +---------------- .../native/src/internal/Synchronized.kt | 2 -- .../native/test/ConcurrentTestUtilities.kt | 4 --- 10 files changed, 4 insertions(+), 101 deletions(-) diff --git a/gradle/compile-native-multiplatform.gradle b/gradle/compile-native-multiplatform.gradle index 75e808bad8..91a6b62c6b 100644 --- a/gradle/compile-native-multiplatform.gradle +++ b/gradle/compile-native-multiplatform.gradle @@ -7,11 +7,6 @@ import static KotlinVersion.isKotlinVersionAtLeast project.ext.nativeMainSets = [] project.ext.nativeTestSets = [] -// TODO: this block should be removed when Kotlin version is updated to 1.9.20 -// Right now it is used for conditional removal of native targets that will be removed in 1.9.20 (iosArm32, watchosX86) -// and is necessary for testing of Kotlin dev builds. -def enableDeprecatedNativeTargets = !isKotlinVersionAtLeast(ext.kotlin_version, 1, 9, 20) - kotlin { targets { delegate.metaClass.addTarget = { preset -> @@ -48,12 +43,6 @@ kotlin { addTarget(presets.androidNativeX64) addTarget(presets.mingwX64) addTarget(presets.watchosDeviceArm64) - - // Deprecated, but were provided by coroutine; can be removed only when K/N drops the target - if (enableDeprecatedNativeTargets) { - addTarget(presets.iosArm32) - addTarget(presets.watchosX86) - } } sourceSets { diff --git a/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt b/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt index 66d65f9e2d..4835f7968e 100644 --- a/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt +++ b/kotlinx-coroutines-core/concurrent/src/CompletionHandler.kt @@ -6,20 +6,12 @@ package kotlinx.coroutines import kotlinx.coroutines.internal.* -// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 -// New 'CompletionHandler` supertype is added compared to the expect declaration. -// Probably we can add it to JS and common too, to avoid the suppression/opt-in -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") internal actual abstract class CompletionHandlerBase actual constructor() : LockFreeLinkedListNode(), CompletionHandler { actual abstract override fun invoke(cause: Throwable?) } internal actual inline val CompletionHandlerBase.asHandler: CompletionHandler get() = this -// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 -// New 'CompletionHandler` supertype is added compared to the expect declaration. -// Probably we can add it to JS and common too, to avoid the suppression/opt-in -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") internal actual abstract class CancelHandlerBase actual constructor() : CompletionHandler { actual abstract override fun invoke(cause: Throwable?) } diff --git a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt index cbc83a6400..cfcd5be81e 100644 --- a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt +++ b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt @@ -42,11 +42,7 @@ internal val CONDITION_FALSE: Any = Symbol("CONDITION_FALSE") * * @suppress **This is unstable API and it is subject to change.** */ -@Suppress( - "LeakingThis", - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING" -) +@Suppress("LeakingThis") @InternalCoroutinesApi public actual open class LockFreeLinkedListNode { private val _next = atomic(this) // Node | Removed | OpDescriptor @@ -72,20 +68,14 @@ public actual open class LockFreeLinkedListNode { } } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") @PublishedApi internal inline fun makeCondAddOp(node: Node, crossinline condition: () -> Boolean): CondAddOp = object : CondAddOp(node) { override fun prepare(affected: Node): Any? = if (condition()) null else CONDITION_FALSE } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("MODALITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION_WARNING") public actual open val isRemoved: Boolean get() = next is Removed - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") // LINEARIZABLE. Returns Node | Removed public val next: Any get() { _next.loop { next -> @@ -176,8 +166,6 @@ public actual open class LockFreeLinkedListNode { * Where `==>` denotes linearization point. * Returns `false` if `next` was not following `this` node. */ - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") @PublishedApi internal fun addNext(node: Node, next: Node): Boolean { node._prev.lazySet(this) @@ -188,8 +176,6 @@ public actual open class LockFreeLinkedListNode { return true } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") // returns UNDECIDED, SUCCESS or FAILURE @PublishedApi internal fun tryCondAddNext(node: Node, next: Node, condAdd: CondAddOp): Int { @@ -213,8 +199,6 @@ public actual open class LockFreeLinkedListNode { public actual open fun remove(): Boolean = removeOrNext() == null - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") // returns null if removed successfully or next node if this node is already removed @PublishedApi internal fun removeOrNext(): Node? { @@ -271,8 +255,6 @@ public actual open class LockFreeLinkedListNode { } } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") protected open fun nextIfRemoved(): Node? = (next as? Removed)?.ref /** @@ -329,8 +311,6 @@ public actual open class LockFreeLinkedListNode { } } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") internal fun validateNode(prev: Node, next: Node) { assert { prev === this._prev.value } assert { next === this._next.value } @@ -351,8 +331,6 @@ internal fun Any.unwrap(): Node = (this as? Removed)?.ref ?: this as Node * * @suppress **This is unstable API and it is subject to change.** */ -// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() { public actual val isEmpty: Boolean get() = next === this @@ -373,12 +351,8 @@ public actual open class LockFreeLinkedListHead : LockFreeLinkedListNode() { // optimization: because head is never removed, we don't have to read _next.value to check these: override val isRemoved: Boolean get() = false - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION") override fun nextIfRemoved(): Node? = null - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") internal fun validate() { var prev: Node = this var cur: Node = next as Node diff --git a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt index aa46e5a814..c4b1fddd03 100644 --- a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt +++ b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt @@ -9,15 +9,8 @@ package kotlinx.coroutines.internal import kotlinx.coroutines.* private typealias Node = LinkedListNode + /** @suppress **This is unstable API and it is subject to change.** */ -@Suppress( - // :TODO: Remove when fixed: https://youtrack.jetbrains.com/issue/KT-23703 - "NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS", - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING", - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING" -) public actual typealias LockFreeLinkedListNode = LinkedListNode /** @suppress **This is unstable API and it is subject to change.** */ diff --git a/kotlinx-coroutines-core/jvm/src/EventLoop.kt b/kotlinx-coroutines-core/jvm/src/EventLoop.kt index 5a8a572789..147d62c4f7 100644 --- a/kotlinx-coroutines-core/jvm/src/EventLoop.kt +++ b/kotlinx-coroutines-core/jvm/src/EventLoop.kt @@ -8,11 +8,8 @@ import kotlinx.coroutines.Runnable import kotlinx.coroutines.scheduling.* import kotlinx.coroutines.scheduling.CoroutineScheduler -// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") internal actual abstract class EventLoopImplPlatform: EventLoop() { - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") + protected abstract val thread: Thread protected actual fun unpark() { @@ -21,8 +18,6 @@ internal actual abstract class EventLoopImplPlatform: EventLoop() { unpark(thread) } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("MODALITY_CHANGED_IN_NON_FINAL_EXPECT_CLASSIFIER_ACTUALIZATION_WARNING") protected actual open fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask) { DefaultExecutor.schedule(now, delayedTask) } diff --git a/kotlinx-coroutines-core/jvm/src/Executors.kt b/kotlinx-coroutines-core/jvm/src/Executors.kt index c236d637c4..121ba3f443 100644 --- a/kotlinx-coroutines-core/jvm/src/Executors.kt +++ b/kotlinx-coroutines-core/jvm/src/Executors.kt @@ -37,12 +37,6 @@ public abstract class ExecutorCoroutineDispatcher: CoroutineDispatcher(), Closea public abstract override fun close() } -@Suppress( - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING", - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING" -) @ExperimentalCoroutinesApi public actual typealias CloseableCoroutineDispatcher = ExecutorCoroutineDispatcher diff --git a/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt b/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt index bf5e066984..6a00f45f13 100644 --- a/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt +++ b/kotlinx-coroutines-core/jvm/src/SchedulerTask.kt @@ -6,12 +6,8 @@ package kotlinx.coroutines import kotlinx.coroutines.scheduling.* -// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") internal actual typealias SchedulerTask = Task -// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") internal actual typealias SchedulerTaskContext = TaskContext @Suppress("EXTENSION_SHADOWED_BY_MEMBER") diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt index 8153a97667..09b6686c1e 100644 --- a/kotlinx-coroutines-core/jvm/test/TestBase.kt +++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt @@ -54,10 +54,6 @@ public actual typealias TestResult = Unit * } * ``` */ -@Suppress( - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING" -) public actual open class TestBase(private var disableOutCheck: Boolean) { actual constructor(): this(false) @@ -83,13 +79,10 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { * Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not * complete successfully even if this exception is consumed somewhere in the test. */ - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 public actual fun error(message: Any, cause: Throwable?): Nothing { throw makeError(message, cause) } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") public fun hasError() = error.get() != null private fun makeError(message: Any, cause: Throwable? = null): IllegalStateException = @@ -113,8 +106,6 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { * Throws [IllegalStateException] when `value` is false like `check` in stdlib, but also ensures that the * test will not complete successfully even if this exception is consumed somewhere in the test. */ - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") public inline fun check(value: Boolean, lazyMessage: () -> Any) { if (!value) error(lazyMessage()) } @@ -162,16 +153,12 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { } }) - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") fun println(message: Any?) { if (disableOutCheck) kotlin.io.println(message) else previousOut.println(message) } @Before - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") fun before() { initPoolsBeforeTest() threadsBefore = currentThreads() @@ -187,8 +174,6 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { } } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") @After fun onCompletion() { // onCompletion should not throw exceptions before it finishes all cleanup, so that other tests always @@ -216,22 +201,17 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { error.get()?.let { throw it } } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") fun initPoolsBeforeTest() { DefaultScheduler.usePrivateScheduler() } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") fun shutdownPoolsAfterTest() { DefaultScheduler.shutdown(SHUTDOWN_TIMEOUT) DefaultExecutor.shutdownForTests(SHUTDOWN_TIMEOUT) DefaultScheduler.restore() } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") + @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun runTest( expected: ((Throwable) -> Boolean)? = null, unhandled: List<(Throwable) -> Boolean> = emptyList(), @@ -265,16 +245,12 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { error("Too few unhandled exceptions $exCount, expected ${unhandled.size}") } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") protected inline fun assertFailsWith(block: () -> Unit): T { val result = runCatching(block) assertTrue(result.exceptionOrNull() is T, "Expected ${T::class}, but had $result") return result.exceptionOrNull()!! as T } - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - @Suppress("NON_ACTUAL_MEMBER_DECLARED_IN_EXPECT_NON_FINAL_CLASSIFIER_ACTUALIZATION_WARNING") protected suspend fun currentDispatcher() = coroutineContext[ContinuationInterceptor]!! } diff --git a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt index c3162497f4..8a8ecfe393 100644 --- a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt +++ b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt @@ -11,8 +11,6 @@ import kotlinx.atomicfu.locks.withLock as withLock2 * @suppress **This an internal API and should not be used from general code.** */ @InternalCoroutinesApi -// fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") public actual typealias SynchronizedObject = kotlinx.atomicfu.locks.SynchronizedObject /** diff --git a/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt b/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt index b14f035b7e..6690972dfa 100644 --- a/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt +++ b/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt @@ -25,10 +25,6 @@ private object BlackHole { var sink = 1 } -@Suppress( - // fixme replace the suppress with AllowDifferentMembersInActual once stdlib is updated to 1.9.20 https://github.com/Kotlin/kotlinx.coroutines/issues/3846 - "ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_MEMBERS_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING", -) internal actual typealias SuppressSupportingThrowable = SuppressSupportingThrowableImpl actual val Throwable.suppressed: Array From 3dd48ace1fda25b298ca9c30532f4bb808be2d45 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Wed, 8 Nov 2023 19:55:15 +0300 Subject: [PATCH 30/53] Specify that `subscriptionCount` not conflating is a guarantee (#3934) Addresses the new comment to #2871 --- kotlinx-coroutines-core/common/src/flow/SharedFlow.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt index 1ded0a1750..588560e2c3 100644 --- a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt +++ b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt @@ -231,7 +231,11 @@ public interface MutableSharedFlow : SharedFlow, FlowCollector { * .launchIn(scope) // launch it * ``` * - * Implementation note: the resulting flow **does not** conflate subscription count. + * Usually, [StateFlow] conflates values, but [subscriptionCount] is not conflated. + * This is done so that any subscribers that need to be notified when subscribers appear do + * reliably observe it. With conflation, if a single subscriber appeared and immediately left, those + * collecting [subscriptionCount] could fail to notice it due to `0` immediately conflating the + * subscription count. */ public val subscriptionCount: StateFlow From ff95ab767c39a5b2110be66e5efbce6ddbb5c2f6 Mon Sep 17 00:00:00 2001 From: Kevin Cianfarini Date: Wed, 15 Nov 2023 13:17:25 -0500 Subject: [PATCH 31/53] Properly round `Duration` instances to milliseconds (#3921) Prior to this commit Durations used for delays or timeouts lost their nanosecond granularity during the conversion to a millisecond Long value. This effectively meant that delays could resume at most a millisecond in advance. This commit solves this by rounding a Duration with nanosecond components up to the next largest millisecond. Fixes #3920 --- kotlinx-coroutines-core/common/src/Delay.kt | 15 ++-- .../common/test/DurationToMillisTest.kt | 69 +++++++++++++++++++ 2 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 kotlinx-coroutines-core/common/test/DurationToMillisTest.kt diff --git a/kotlinx-coroutines-core/common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt index ba06d9778d..313e87314d 100644 --- a/kotlinx-coroutines-core/common/src/Delay.kt +++ b/kotlinx-coroutines-core/common/src/Delay.kt @@ -7,6 +7,7 @@ package kotlinx.coroutines import kotlinx.coroutines.selects.* import kotlin.coroutines.* import kotlin.time.* +import kotlin.time.Duration.Companion.nanoseconds /** * This dispatcher _feature_ is implemented by [CoroutineDispatcher] implementations that natively support @@ -106,7 +107,7 @@ internal interface DelayWithTimeoutDiagnostics : Delay { public suspend fun awaitCancellation(): Nothing = suspendCancellableCoroutine {} /** - * Delays coroutine for a given time without blocking a thread and resumes it after a specified time. + * Delays coroutine for at least the given time without blocking a thread and resumes it after a specified time. * If the given [timeMillis] is non-positive, this function returns immediately. * * This suspending function is cancellable. @@ -133,7 +134,7 @@ public suspend fun delay(timeMillis: Long) { } /** - * Delays coroutine for a given [duration] without blocking a thread and resumes it after the specified time. + * Delays coroutine for at least the given [duration] without blocking a thread and resumes it after the specified time. * If the given [duration] is non-positive, this function returns immediately. * * This suspending function is cancellable. @@ -154,8 +155,10 @@ public suspend fun delay(duration: Duration): Unit = delay(duration.toDelayMilli internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor) as? Delay ?: DefaultDelay /** - * Convert this duration to its millisecond value. - * Positive durations are coerced at least `1`. + * Convert this duration to its millisecond value. Durations which have a nanosecond component less than + * a single millisecond will be rounded up to the next largest millisecond. */ -internal fun Duration.toDelayMillis(): Long = - if (this > Duration.ZERO) inWholeMilliseconds.coerceAtLeast(1) else 0 +internal fun Duration.toDelayMillis(): Long = when (isPositive()) { + true -> plus(999_999L.nanoseconds).inWholeMilliseconds + false -> 0L +} diff --git a/kotlinx-coroutines-core/common/test/DurationToMillisTest.kt b/kotlinx-coroutines-core/common/test/DurationToMillisTest.kt new file mode 100644 index 0000000000..e2ea43dd3c --- /dev/null +++ b/kotlinx-coroutines-core/common/test/DurationToMillisTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package kotlinx.coroutines + +import kotlin.test.* +import kotlin.time.* +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.nanoseconds +import kotlin.time.Duration.Companion.seconds + +class DurationToMillisTest { + + @Test + fun testNegativeDurationCoercedToZeroMillis() { + assertEquals(0L, (-1).seconds.toDelayMillis()) + } + + @Test + fun testZeroDurationCoercedToZeroMillis() { + assertEquals(0L, 0.seconds.toDelayMillis()) + } + + @Test + fun testOneNanosecondCoercedToOneMillisecond() { + assertEquals(1L, 1.nanoseconds.toDelayMillis()) + } + + @Test + fun testOneSecondCoercedTo1000Milliseconds() { + assertEquals(1_000L, 1.seconds.toDelayMillis()) + } + + @Test + fun testMixedComponentDurationRoundedUpToNextMillisecond() { + assertEquals(999L, (998.milliseconds + 75909.nanoseconds).toDelayMillis()) + } + + @Test + fun testOneExtraNanosecondRoundedUpToNextMillisecond() { + assertEquals(999L, (998.milliseconds + 1.nanoseconds).toDelayMillis()) + } + + @Test + fun testInfiniteDurationCoercedToLongMaxValue() { + assertEquals(Long.MAX_VALUE, Duration.INFINITE.toDelayMillis()) + } + + @Test + fun testNegativeInfiniteDurationCoercedToZero() { + assertEquals(0L, (-Duration.INFINITE).toDelayMillis()) + } + + @Test + fun testNanosecondOffByOneInfinityDoesNotOverflow() { + assertEquals(Long.MAX_VALUE / 1_000_000, (Long.MAX_VALUE - 1L).nanoseconds.toDelayMillis()) + } + + @Test + fun testMillisecondOffByOneInfinityDoesNotIncrement() { + assertEquals((Long.MAX_VALUE / 2) - 1, ((Long.MAX_VALUE / 2) - 1).milliseconds.toDelayMillis()) + } + + @Test + fun testOutOfBoundsNanosecondsButFiniteDoesNotIncrement() { + val milliseconds = Long.MAX_VALUE / 10 + assertEquals(milliseconds, milliseconds.milliseconds.toDelayMillis()) + } +} From 142f7973e78b421962c2cebe92d25d6f9a2e8b0d Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 21 Nov 2023 14:58:26 +0100 Subject: [PATCH 32/53] Introduce jvmBenchmarks into kotlinx-coroutines-core (#3946) It allows us to benchmark `internal` API unavailable otherwise and allows us to backport K2 changes properly. Only benchmarks that somehow leverage `internal` API are moved in the new source-set --- kotlinx-coroutines-core/build.gradle | 11 +++++++++++ kotlinx-coroutines-core/jvmBenchmark/README.md | 15 +++++++++++++++ .../kotlin/kotlinx/coroutines/BenchmarkUtils.kt | 17 +++++++++++++++++ .../kotlinx/coroutines}/SemaphoreBenchmark.kt | 8 +++----- .../ChannelProducerConsumerBenchmark.kt | 9 ++++----- .../coroutines/channels}/SelectBenchmark.kt | 4 ++-- .../coroutines/channels}/SimpleChannel.kt | 7 ++----- .../channels}/SimpleChannelBenchmark.kt | 4 ++-- .../coroutines}/flow/TakeWhileBenchmark.kt | 11 +++++------ 9 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 kotlinx-coroutines-core/jvmBenchmark/README.md create mode 100644 kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/BenchmarkUtils.kt rename {benchmarks/src/jmh/kotlin/benchmarks => kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines}/SemaphoreBenchmark.kt (91%) rename {benchmarks/src/jmh/kotlin/benchmarks => kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels}/ChannelProducerConsumerBenchmark.kt (94%) rename {benchmarks/src/jmh/kotlin/benchmarks/tailcall => kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels}/SelectBenchmark.kt (90%) rename {benchmarks/src/jmh/kotlin/benchmarks/tailcall => kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels}/SimpleChannel.kt (92%) rename {benchmarks/src/jmh/kotlin/benchmarks/tailcall => kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels}/SimpleChannelBenchmark.kt (92%) rename {benchmarks/src/jmh/kotlin/benchmarks => kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines}/flow/TakeWhileBenchmark.kt (89%) diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index 86350ce546..2c7f8883ae 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -2,6 +2,10 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +plugins { + id 'org.jetbrains.kotlinx.benchmark' version '0.4.9' +} + apply plugin: 'org.jetbrains.kotlin.multiplatform' apply plugin: 'org.jetbrains.dokka' @@ -167,6 +171,13 @@ kotlin { // For animal sniffer withJava() + compilations.create('benchmark') { associateWith(compilations.main) } + } +} + +benchmark { + targets { + register("jvmBenchmark") } } diff --git a/kotlinx-coroutines-core/jvmBenchmark/README.md b/kotlinx-coroutines-core/jvmBenchmark/README.md new file mode 100644 index 0000000000..78eeb6c01d --- /dev/null +++ b/kotlinx-coroutines-core/jvmBenchmark/README.md @@ -0,0 +1,15 @@ +## kotlinx-coroutines-core benchmarks + +This source-set contains benchmarks that leverage `internal` API (e.g. `suspendCancellableCoroutineReusable`) +and thus cannot be written in `benchmarks` module. + +This is an interim solution unless we introduce clear separation of responsibilities in benchmark modules +and decide on their usability. + + +### Usage + +``` +./gradlew :kotlinx-coroutines-core:jvmBenchmarkBenchmarkJar +java -jar kotlinx-coroutines-core/build/benchmarks/jvmBenchmark/jars/kotlinx-coroutines-core-jvmBenchmark-jmh-1.7.2-SNAPSHOT-JMH.jar +``` diff --git a/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/BenchmarkUtils.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/BenchmarkUtils.kt new file mode 100644 index 0000000000..714aad114c --- /dev/null +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/BenchmarkUtils.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import java.util.concurrent.* + +public fun doGeomDistrWork(work: Int) { + // We use geometric distribution here. We also checked on macbook pro 13" (2017) that the resulting work times + // are distributed geometrically, see https://github.com/Kotlin/kotlinx.coroutines/pull/1464#discussion_r355705325 + val p = 1.0 / work + val r = ThreadLocalRandom.current() + while (true) { + if (r.nextDouble() < p) break + } +} diff --git a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt similarity index 91% rename from benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt rename to kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt index 6826b7a1a3..df82690771 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/SemaphoreBenchmark.kt @@ -1,10 +1,9 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package benchmarks +package kotlinx.coroutines -import benchmarks.common.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.scheduling.* @@ -80,8 +79,7 @@ open class SemaphoreBenchmark { } enum class SemaphoreBenchDispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) { - // FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }), - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }), DEFAULT({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) }) } diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/ChannelProducerConsumerBenchmark.kt similarity index 94% rename from benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt rename to kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/ChannelProducerConsumerBenchmark.kt index 0aa218e824..7bc0d2b2be 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/ChannelProducerConsumerBenchmark.kt @@ -1,16 +1,16 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package benchmarks +package kotlinx.coroutines.channels -import benchmarks.common.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.scheduling.* import kotlinx.coroutines.selects.select import org.openjdk.jmh.annotations.* import java.lang.Integer.max +import java.util.concurrent.ForkJoinPool import java.util.concurrent.Phaser import java.util.concurrent.TimeUnit @@ -136,8 +136,7 @@ open class ChannelProducerConsumerBenchmark { } enum class DispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) { - //FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }), - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }), DEFAULT({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) }) } diff --git a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SelectBenchmark.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SelectBenchmark.kt similarity index 90% rename from benchmarks/src/jmh/kotlin/benchmarks/tailcall/SelectBenchmark.kt rename to kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SelectBenchmark.kt index cb4d39eed6..e2bc9e4abf 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SelectBenchmark.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SelectBenchmark.kt @@ -1,8 +1,8 @@ /* - * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package benchmarks.tailcall +package kotlinx.coroutines.channels import kotlinx.coroutines.* import kotlinx.coroutines.channels.* diff --git a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannel.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SimpleChannel.kt similarity index 92% rename from benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannel.kt rename to kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SimpleChannel.kt index 1f71d8dc19..1afb0f0464 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannel.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SimpleChannel.kt @@ -1,14 +1,13 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package benchmarks.tailcall +package kotlinx.coroutines.channels import kotlinx.coroutines.* import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* -@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") public abstract class SimpleChannel { companion object { const val NULL_SURROGATE: Int = -1 @@ -83,13 +82,11 @@ class CancellableChannel : SimpleChannel() { } class CancellableReusableChannel : SimpleChannel() { - @Suppress("INVISIBLE_MEMBER") override suspend fun suspendReceive(): Int = suspendCancellableCoroutineReusable { consumer = it.intercepted() COROUTINE_SUSPENDED } - @Suppress("INVISIBLE_MEMBER") override suspend fun suspendSend(element: Int) = suspendCancellableCoroutineReusable { enqueuedValue = element producer = it.intercepted() diff --git a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SimpleChannelBenchmark.kt similarity index 92% rename from benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt rename to kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SimpleChannelBenchmark.kt index 9654b6dabe..233cecff4e 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SimpleChannelBenchmark.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/channels/SimpleChannelBenchmark.kt @@ -1,8 +1,8 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package benchmarks.tailcall +package kotlinx.coroutines.channels import kotlinx.coroutines.* import org.openjdk.jmh.annotations.* diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/flow/TakeWhileBenchmark.kt similarity index 89% rename from benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt rename to kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/flow/TakeWhileBenchmark.kt index 7501e2c419..6dad29e51f 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/flow/TakeWhileBenchmark.kt +++ b/kotlinx-coroutines-core/jvmBenchmark/kotlin/kotlinx/coroutines/flow/TakeWhileBenchmark.kt @@ -1,16 +1,15 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") - -package benchmarks.flow +package kotlinx.coroutines.flow import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.internal.* +import kotlinx.coroutines.flow.internal.AbortFlowException +import kotlinx.coroutines.flow.internal.unsafeFlow import org.openjdk.jmh.annotations.* -import java.util.concurrent.TimeUnit +import java.util.concurrent.* @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) From 9e0cfe05b8efd184a5d54340449320378b24b3f5 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 21 Nov 2023 15:11:31 +0100 Subject: [PATCH 33/53] Remove memoryModel = experimental (#3947) It's no longer experimental --- kotlinx-coroutines-core/build.gradle | 10 ++++------ kotlinx-coroutines-test/build.gradle.kts | 1 - 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index 2c7f8883ae..7c26e9d280 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -110,8 +110,8 @@ if (rootProject.ext.native_targets_enabled) { kotlin { /* * Configure two test runs: - * 1) New memory model, Main thread - * 2) New memory model, BG thread (required for Dispatchers.Main tests on Darwin) + * 1) Main thread + * 2) BG thread (required for Dispatchers.Main tests on Darwin) * * All new MM targets are build with optimize = true to have stress tests properly run. */ @@ -120,15 +120,13 @@ kotlin { optimized = true // Test for memory leaks using a special entry point that does not exit but returns from main freeCompilerArgs += ["-e", "kotlinx.coroutines.mainNoExit"] - binaryOptions["memoryModel"] = "experimental" } - binaries.test("workerWithNewMM", [DEBUG]) { + binaries.test("workerTest", [DEBUG]) { def thisTest = it optimized = true freeCompilerArgs += ["-e", "kotlinx.coroutines.mainBackground"] - binaryOptions["memoryModel"] = "experimental" - testRuns.create("workerWithNewMM") { + testRuns.create("workerTest") { setExecutionSourceFrom(thisTest) executionTask.configure { targetName = "$targetName worker with new MM" } } diff --git a/kotlinx-coroutines-test/build.gradle.kts b/kotlinx-coroutines-test/build.gradle.kts index c968fc4991..981e04ad64 100644 --- a/kotlinx-coroutines-test/build.gradle.kts +++ b/kotlinx-coroutines-test/build.gradle.kts @@ -16,7 +16,6 @@ kotlin { targets.withType(KotlinNativeTargetWithTests::class.java).configureEach { binaries.getTest("DEBUG").apply { optimized = true - binaryOptions["memoryModel"] = "experimental" } } From d559909f685cc964d5d8d52bc994fa7678fee124 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 29 Jun 2023 12:58:39 +0200 Subject: [PATCH 34/53] Rename the `completion` field in `SafeCollector` K2 now runs checks more aggressively and reports name-clash without override for invisible member/invisible reference suppresses --- .../jvm/src/flow/internal/SafeCollector.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt b/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt index c6b7ea92ce..6bfd4ce847 100644 --- a/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt +++ b/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt @@ -23,7 +23,7 @@ internal actual class SafeCollector actual constructor( @JvmField internal actual val collectContext: CoroutineContext ) : FlowCollector, ContinuationImpl(NoOpContinuation, EmptyCoroutineContext), CoroutineStackFrame { - override val callerFrame: CoroutineStackFrame? get() = completion as? CoroutineStackFrame + override val callerFrame: CoroutineStackFrame? get() = completion_ as? CoroutineStackFrame override fun getStackTraceElement(): StackTraceElement? = null @@ -32,20 +32,20 @@ internal actual class SafeCollector actual constructor( // Either context of the last emission or wrapper 'DownstreamExceptionContext' private var lastEmissionContext: CoroutineContext? = null - // Completion if we are currently suspended or within completion body or null otherwise - private var completion: Continuation? = null + // Completion if we are currently suspended or within completion_ body or null otherwise + private var completion_: Continuation? = null /* * This property is accessed in two places: * * ContinuationImpl invokes this in its `releaseIntercepted` as `context[ContinuationInterceptor]!!` - * * When we are within a callee, it is used to create its continuation object with this collector as completion + * * When we are within a callee, it is used to create its continuation object with this collector as completion_ */ override val context: CoroutineContext get() = lastEmissionContext ?: EmptyCoroutineContext override fun invokeSuspend(result: Result): Any { result.onFailure { lastEmissionContext = DownstreamExceptionContext(it, context) } - completion?.resumeWith(result as Result) + completion_?.resumeWith(result as Result) return COROUTINE_SUSPENDED } @@ -57,7 +57,7 @@ internal actual class SafeCollector actual constructor( /** * This is a crafty implementation of state-machine reusing. * First it checks that it is not used concurrently (which we explicitly prohibit) and - * then just cache an instance of the completion in order to avoid extra allocation on each emit, + * then just cache an instance of the completion_ in order to avoid extra allocation on each emit, * making it effectively garbage-free on its hot-path. */ actual override suspend fun emit(value: T) { @@ -83,14 +83,14 @@ internal actual class SafeCollector actual constructor( checkContext(currentContext, previousContext, value) lastEmissionContext = currentContext } - completion = uCont + completion_ = uCont val result = emitFun(collector as FlowCollector, value, this as Continuation) /* * If the callee hasn't suspended, that means that it won't (it's forbidden) call 'resumeWith` (-> `invokeSuspend`) * and we don't have to retain a strong reference to it to avoid memory leaks. */ if (result != COROUTINE_SUSPENDED) { - completion = null + completion_ = null } return result } From 2d852a59406cb0ea8d3631b3bf4bc881e8887427 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Thu, 29 Jun 2023 13:03:10 +0200 Subject: [PATCH 35/53] Make SchedulerTask a proper interface `actual typealias` no longer works for `expect interface` --- kotlinx-coroutines-core/js/src/SchedulerTask.kt | 7 ++++--- kotlinx-coroutines-core/native/src/SchedulerTask.kt | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/kotlinx-coroutines-core/js/src/SchedulerTask.kt b/kotlinx-coroutines-core/js/src/SchedulerTask.kt index c0ecc4f2da..f8569ba505 100644 --- a/kotlinx-coroutines-core/js/src/SchedulerTask.kt +++ b/kotlinx-coroutines-core/js/src/SchedulerTask.kt @@ -6,10 +6,11 @@ package kotlinx.coroutines internal actual abstract class SchedulerTask : Runnable -@Suppress("ACTUAL_WITHOUT_EXPECT") -internal actual typealias SchedulerTaskContext = Unit +internal actual interface SchedulerTaskContext { } -internal actual val SchedulerTask.taskContext: SchedulerTaskContext get() = Unit +private object TaskContext: SchedulerTaskContext { } + +internal actual val SchedulerTask.taskContext: SchedulerTaskContext get() = TaskContext @Suppress("NOTHING_TO_INLINE") internal actual inline fun SchedulerTaskContext.afterTask() {} diff --git a/kotlinx-coroutines-core/native/src/SchedulerTask.kt b/kotlinx-coroutines-core/native/src/SchedulerTask.kt index 781e32213c..9b2b68a02a 100644 --- a/kotlinx-coroutines-core/native/src/SchedulerTask.kt +++ b/kotlinx-coroutines-core/native/src/SchedulerTask.kt @@ -6,10 +6,11 @@ package kotlinx.coroutines internal actual abstract class SchedulerTask : Runnable -@Suppress("ACTUAL_WITHOUT_EXPECT") -internal actual typealias SchedulerTaskContext = Unit +internal actual interface SchedulerTaskContext { } -internal actual val SchedulerTask.taskContext: SchedulerTaskContext get() = kotlin.Unit +private object TaskContext: SchedulerTaskContext { } + +internal actual val SchedulerTask.taskContext: SchedulerTaskContext get() = TaskContext @Suppress("NOTHING_TO_INLINE") internal actual inline fun SchedulerTaskContext.afterTask() {} From 2ec8c2811475cc0e4a02af12bd8ce5041e3650c0 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 24 Nov 2023 11:31:05 +0100 Subject: [PATCH 36/53] Add INVISIBLE_REFERENCE to every INVISIBLE_MEMBER suppression --- .../common/src/TestCoroutineDispatchers.kt | 2 +- kotlinx-coroutines-test/common/src/TestScope.kt | 4 ++-- .../common/src/internal/ExceptionCollector.kt | 2 +- .../common/src/internal/TestMainDispatcher.kt | 4 ++-- kotlinx-coroutines-test/common/test/RunTestTest.kt | 4 ++-- kotlinx-coroutines-test/js/src/internal/TestMainDispatcher.kt | 2 +- .../jvm/src/migration/TestCoroutineExceptionHandler.kt | 2 +- .../jvm/test/migration/RunTestLegacyScopeTest.kt | 2 +- .../native/src/internal/TestMainDispatcher.kt | 2 +- reactive/kotlinx-coroutines-reactive/src/Publish.kt | 2 +- reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt | 4 ++-- reactive/kotlinx-coroutines-rx2/src/RxObservable.kt | 4 ++-- reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt | 4 ++-- reactive/kotlinx-coroutines-rx3/src/RxObservable.kt | 4 ++-- reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt | 2 +- 15 files changed, 22 insertions(+), 22 deletions(-) diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt b/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt index 3777cd26f8..84fede4946 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt @@ -93,7 +93,7 @@ private class UnconfinedTestDispatcherImpl( override fun isDispatchNeeded(context: CoroutineContext): Boolean = false - @Suppress("INVISIBLE_MEMBER") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 override fun dispatch(context: CoroutineContext, block: Runnable) { checkSchedulerInContext(scheduler, context) scheduler.sendDispatchEvent(context) diff --git a/kotlinx-coroutines-test/common/src/TestScope.kt b/kotlinx-coroutines-test/common/src/TestScope.kt index ae644ae804..7089255669 100644 --- a/kotlinx-coroutines-test/common/src/TestScope.kt +++ b/kotlinx-coroutines-test/common/src/TestScope.kt @@ -231,7 +231,7 @@ internal class TestScopeImpl(context: CoroutineContext) : * However, we also want [uncaughtExceptions] to be queried after the callback is registered, * because the exception collector will be able to report the exceptions that arrived before this test but * after the previous one, and learning about such exceptions as soon is possible is nice. */ - @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 run { ensurePlatformExceptionHandlerLoaded(ExceptionCollector) } if (catchNonTestRelatedExceptions) { ExceptionCollector.addOnExceptionCallback(lock, this::reportException) @@ -288,7 +288,7 @@ internal class TestScopeImpl(context: CoroutineContext) : if (finished) { throw throwable } else { - @Suppress("INVISIBLE_MEMBER") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 for (existingThrowable in uncaughtExceptions) { // avoid reporting exceptions that already were reported. if (unwrap(throwable) == unwrap(existingThrowable)) diff --git a/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt b/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt index 70fcb9487f..6387e791bd 100644 --- a/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt +++ b/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt @@ -81,7 +81,7 @@ internal object ExceptionCollector : AbstractCoroutineContextElement(CoroutineEx return executedACallback } - @Suppress("INVISIBLE_MEMBER") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 override fun handleException(context: CoroutineContext, exception: Throwable) { if (handleException(exception)) { throw ExceptionSuccessfullyProcessed diff --git a/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt b/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt index 411699b9d8..acb45e0899 100644 --- a/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt +++ b/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt @@ -91,9 +91,9 @@ internal class TestMainDispatcher(delegate: CoroutineDispatcher): } } -@Suppress("INVISIBLE_MEMBER") +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 private val defaultDelay inline get() = DefaultDelay -@Suppress("INVISIBLE_MEMBER") +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 internal expect fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher diff --git a/kotlinx-coroutines-test/common/test/RunTestTest.kt b/kotlinx-coroutines-test/common/test/RunTestTest.kt index f4ad53abf1..6f31cca349 100644 --- a/kotlinx-coroutines-test/common/test/RunTestTest.kt +++ b/kotlinx-coroutines-test/common/test/RunTestTest.kt @@ -166,7 +166,7 @@ class RunTestTest { it() fail("unreached") } catch (e: UncompletedCoroutinesError) { - @Suppress("INVISIBLE_MEMBER") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 val suppressed = unwrap(e).suppressedExceptions assertEquals(1, suppressed.size, "$suppressed") assertIs(suppressed[0]).also { @@ -207,7 +207,7 @@ class RunTestTest { fn() fail("unreached") } catch (e: UncompletedCoroutinesError) { - @Suppress("INVISIBLE_MEMBER") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 val suppressed = unwrap(e).suppressedExceptions assertEquals(1, suppressed.size, "$suppressed") assertIs(suppressed[0]).also { diff --git a/kotlinx-coroutines-test/js/src/internal/TestMainDispatcher.kt b/kotlinx-coroutines-test/js/src/internal/TestMainDispatcher.kt index 4d865f83c0..d68cecda70 100644 --- a/kotlinx-coroutines-test/js/src/internal/TestMainDispatcher.kt +++ b/kotlinx-coroutines-test/js/src/internal/TestMainDispatcher.kt @@ -5,7 +5,7 @@ package kotlinx.coroutines.test.internal import kotlinx.coroutines.* -@Suppress("INVISIBLE_MEMBER") +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 internal actual fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher = when (val mainDispatcher = Main) { is TestMainDispatcher -> mainDispatcher diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt index 150055f532..24f2166eb3 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt @@ -55,7 +55,7 @@ public class TestCoroutineExceptionHandler : private val _lock = SynchronizedObject() private var _coroutinesCleanedUp = false - @Suppress("INVISIBLE_MEMBER") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 override fun handleException(context: CoroutineContext, exception: Throwable) { synchronized(_lock) { if (_coroutinesCleanedUp) { diff --git a/kotlinx-coroutines-test/jvm/test/migration/RunTestLegacyScopeTest.kt b/kotlinx-coroutines-test/jvm/test/migration/RunTestLegacyScopeTest.kt index ed5b1577f5..4f05c1920f 100644 --- a/kotlinx-coroutines-test/jvm/test/migration/RunTestLegacyScopeTest.kt +++ b/kotlinx-coroutines-test/jvm/test/migration/RunTestLegacyScopeTest.kt @@ -92,7 +92,7 @@ class RunTestLegacyScopeTest { fn() fail("unreached") } catch (e: UncompletedCoroutinesError) { - @Suppress("INVISIBLE_MEMBER") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 val suppressed = unwrap(e).suppressedExceptions assertEquals(1, suppressed.size) assertIs(suppressed[0]).also { diff --git a/kotlinx-coroutines-test/native/src/internal/TestMainDispatcher.kt b/kotlinx-coroutines-test/native/src/internal/TestMainDispatcher.kt index 4d865f83c0..d68cecda70 100644 --- a/kotlinx-coroutines-test/native/src/internal/TestMainDispatcher.kt +++ b/kotlinx-coroutines-test/native/src/internal/TestMainDispatcher.kt @@ -5,7 +5,7 @@ package kotlinx.coroutines.test.internal import kotlinx.coroutines.* -@Suppress("INVISIBLE_MEMBER") +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 internal actual fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher = when (val mainDispatcher = Main) { is TestMainDispatcher -> mainDispatcher diff --git a/reactive/kotlinx-coroutines-reactive/src/Publish.kt b/reactive/kotlinx-coroutines-reactive/src/Publish.kt index ae85e4186a..a516777860 100644 --- a/reactive/kotlinx-coroutines-reactive/src/Publish.kt +++ b/reactive/kotlinx-coroutines-reactive/src/Publish.kt @@ -84,7 +84,7 @@ public class PublisherCoroutine( // Mutex is locked when either nRequested == 0 or while subscriber.onXXX is being invoked private val mutex: Mutex = Mutex(locked = true) - @Suppress("UNCHECKED_CAST", "INVISIBLE_MEMBER") + @Suppress("UNCHECKED_CAST", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 override val onSend: SelectClause2> get() = SelectClause2Impl( clauseObject = this, regFunc = PublisherCoroutine<*>::registerSelectForSend as RegistrationFunction, diff --git a/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt b/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt index 1a527a3c2b..7e6dcefae2 100644 --- a/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt +++ b/reactive/kotlinx-coroutines-reactive/src/ReactiveFlow.kt @@ -59,7 +59,7 @@ private class PublisherAsFlow( * It's too counter-intuitive to be public, and moving it to Flow companion * will also create undesired effect. */ - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 private val requestSize: Long get() = if (onBufferOverflow != BufferOverflow.SUSPEND) { @@ -208,7 +208,7 @@ public class FlowSubscription( try { consumeFlow() } catch (cause: Throwable) { - @Suppress("INVISIBLE_MEMBER") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 val unwrappedCause = unwrap(cause) if (!cancellationRequested || isActive || unwrappedCause !== getCancellationException()) { try { diff --git a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt index ad8fac71d3..2da5096804 100644 --- a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt +++ b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt @@ -70,7 +70,7 @@ private class RxObservableCoroutine( // Mutex is locked when either nRequested == 0 or while subscriber.onXXX is being invoked private val mutex: Mutex = Mutex() - @Suppress("UNCHECKED_CAST", "INVISIBLE_MEMBER") + @Suppress("UNCHECKED_CAST", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 override val onSend: SelectClause2> get() = SelectClause2Impl( clauseObject = this, regFunc = RxObservableCoroutine<*>::registerSelectForSend as RegistrationFunction, @@ -165,7 +165,7 @@ private class RxObservableCoroutine( if (_signal.value == SIGNALLED) return _signal.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed) - @Suppress("INVISIBLE_MEMBER") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 val unwrappedCause = cause?.let { unwrap(it) } if (unwrappedCause == null) { try { diff --git a/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt b/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt index d7d5f6cfbf..1b65f9b36c 100644 --- a/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt +++ b/reactive/kotlinx-coroutines-rx2/src/RxScheduler.kt @@ -136,7 +136,7 @@ private fun CoroutineScope.scheduleTask( if (delayMillis <= 0) { toSchedule.run() } else { - @Suppress("INVISIBLE_MEMBER") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 ctx.delay.invokeOnTimeout(delayMillis, toSchedule, ctx).let { handle = it } } return disposable @@ -178,4 +178,4 @@ public class SchedulerCoroutineDispatcher( /** @suppress */ override fun hashCode(): Int = System.identityHashCode(scheduler) -} \ No newline at end of file +} diff --git a/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt b/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt index 8ea761c979..4123531a69 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt @@ -70,7 +70,7 @@ private class RxObservableCoroutine( // Mutex is locked when either nRequested == 0 or while subscriber.onXXX is being invoked private val mutex: Mutex = Mutex() - @Suppress("UNCHECKED_CAST", "INVISIBLE_MEMBER") + @Suppress("UNCHECKED_CAST", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 override val onSend: SelectClause2> get() = SelectClause2Impl( clauseObject = this, regFunc = RxObservableCoroutine<*>::registerSelectForSend as RegistrationFunction, @@ -165,7 +165,7 @@ private class RxObservableCoroutine( if (_signal.value == SIGNALLED) return _signal.value = SIGNALLED // we'll signal onError/onCompleted (that the final state -- no CAS needed) - @Suppress("INVISIBLE_MEMBER") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 val unwrappedCause = cause?.let { unwrap(it) } if (unwrappedCause == null) { try { diff --git a/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt b/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt index e7f93868b1..2ef0fc1d92 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt @@ -136,7 +136,7 @@ private fun CoroutineScope.scheduleTask( if (delayMillis <= 0) { toSchedule.run() } else { - @Suppress("INVISIBLE_MEMBER") + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 ctx.delay.invokeOnTimeout(delayMillis, toSchedule, ctx).let { handle = it } } return disposable From 9b2bd3a794879f61f994b7757ac6f23453351829 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 15 Nov 2023 12:22:13 +0100 Subject: [PATCH 37/53] Work around KT-63238 by removing Any.unwrap altogether --- .../concurrent/src/internal/LockFreeLinkedList.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt index cfcd5be81e..533fba6436 100644 --- a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt +++ b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt @@ -85,7 +85,8 @@ public actual open class LockFreeLinkedListNode { } // LINEARIZABLE. Returns next non-removed Node - public actual val nextNode: Node get() = next.unwrap() + public actual val nextNode: Node get() = + next.let { (it as? Removed)?.ref ?: it as Node } // unwraps the `next` node // LINEARIZABLE WHEN THIS NODE IS NOT REMOVED: // Returns prev non-removed Node, makes sure prev is correct (prev.next === this) @@ -323,9 +324,6 @@ private class Removed(@JvmField val ref: Node) { override fun toString(): String = "Removed[$ref]" } -@PublishedApi -internal fun Any.unwrap(): Node = (this as? Removed)?.ref ?: this as Node - /** * Head (sentinel) item of the linked list that is never removed. * From e1342d25998c829c931cb0e7c44ca1b07c15a2af Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 27 Nov 2023 18:07:33 +0100 Subject: [PATCH 38/53] Update Kotlin to 1.9.21 (#3954) * Update Kotlin to 1.9.21 * Update atomicfu to a version released with 1.9.21 to address KT-62515 * Enable K/N atomicfu transformations --- README.md | 10 +++++----- gradle.properties | 5 +++-- integration-testing/gradle.properties | 2 +- kotlinx-coroutines-core/native/test/TestBase.kt | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 25df7aa8b5..90ec53e80b 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ [![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.2)](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.2) -[![Kotlin](https://img.shields.io/badge/kotlin-1.9.20-blue.svg?logo=kotlin)](http://kotlinlang.org) +[![Kotlin](https://img.shields.io/badge/kotlin-1.9.21-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/) Library support for Kotlin coroutines with [multiplatform](#multiplatform) support. -This is a companion version for the Kotlin `1.9.20` release. +This is a companion version for the Kotlin `1.9.21` release. ```kotlin suspend fun main() = coroutineScope { @@ -93,7 +93,7 @@ And make sure that you use the latest Kotlin version: ```xml - 1.9.20 + 1.9.21 ``` @@ -112,10 +112,10 @@ And make sure that you use the latest Kotlin version: ```kotlin plugins { // For build.gradle.kts (Kotlin DSL) - kotlin("jvm") version "1.9.20" + kotlin("jvm") version "1.9.21" // For build.gradle (Groovy DSL) - id "org.jetbrains.kotlin.jvm" version "1.9.20" + id "org.jetbrains.kotlin.jvm" version "1.9.21" } ``` diff --git a/gradle.properties b/gradle.properties index c8d0c19696..2d57149b5d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,12 +5,12 @@ # Kotlin version=1.7.2-SNAPSHOT group=org.jetbrains.kotlinx -kotlin_version=1.9.20 +kotlin_version=1.9.21 # Dependencies junit_version=4.12 junit5_version=5.7.0 -atomicfu_version=0.22.0 +atomicfu_version=0.23.1 knit_version=0.4.0 html_version=0.7.2 lincheck_version=2.18.1 @@ -46,3 +46,4 @@ kotlinx.atomicfu.enableJvmIrTransformation=true # When the flag below is set to `true`, AtomicFU cannot process # usages of `moveForward` in `ConcurrentLinkedList.kt` correctly. kotlinx.atomicfu.enableJsIrTransformation=false +kotlinx.atomicfu.enableNativeIrTransformation=true diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties index b662e51e15..e8e17351cf 100644 --- a/integration-testing/gradle.properties +++ b/integration-testing/gradle.properties @@ -1,4 +1,4 @@ -kotlin_version=1.9.20 +kotlin_version=1.9.21 coroutines_version=1.7.2-SNAPSHOT asm_version=9.3 diff --git a/kotlinx-coroutines-core/native/test/TestBase.kt b/kotlinx-coroutines-core/native/test/TestBase.kt index 173bcc30ae..c25f03569a 100644 --- a/kotlinx-coroutines-core/native/test/TestBase.kt +++ b/kotlinx-coroutines-core/native/test/TestBase.kt @@ -17,8 +17,8 @@ public actual typealias TestResult = Unit public actual open class TestBase actual constructor() { public actual val isBoundByJsTestTimeout = false - private var actionIndex = atomic(0) - private var finished = atomic(false) + private val actionIndex = atomic(0) + private val finished = atomic(false) private var error: Throwable? = null /** From f5b3f96756017e0b7508ba2651e9bc01925e1e3d Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:38:05 +0300 Subject: [PATCH 39/53] Ensure that `println` is properly resolved in the JVM tests (#3955) In K2, the resolution rules changed, and we relied on the specific behavior of the K1 compiler. --- kotlinx-coroutines-core/common/test/TestBase.common.kt | 1 + kotlinx-coroutines-core/js/test/TestBase.kt | 4 ++++ kotlinx-coroutines-core/jvm/test/TestBase.kt | 2 +- kotlinx-coroutines-core/native/test/TestBase.kt | 4 ++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/test/TestBase.common.kt b/kotlinx-coroutines-core/common/test/TestBase.common.kt index 06e71b45b5..3b9aeefab6 100644 --- a/kotlinx-coroutines-core/common/test/TestBase.common.kt +++ b/kotlinx-coroutines-core/common/test/TestBase.common.kt @@ -39,6 +39,7 @@ public expect open class TestBase constructor() { public fun finish(index: Int) public fun ensureFinished() // Ensures that 'finish' was invoked public fun reset() // Resets counter and finish flag. Workaround for parametrized tests absence in common + public fun println(message: Any?) public fun runTest( expected: ((Throwable) -> Boolean)? = null, diff --git a/kotlinx-coroutines-core/js/test/TestBase.kt b/kotlinx-coroutines-core/js/test/TestBase.kt index d50d93da9e..83f6cfd67d 100644 --- a/kotlinx-coroutines-core/js/test/TestBase.kt +++ b/kotlinx-coroutines-core/js/test/TestBase.kt @@ -77,6 +77,10 @@ public actual open class TestBase actual constructor() { finished = false } + actual fun println(message: Any?) { + kotlin.io.println(message) + } + @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun runTest( expected: ((Throwable) -> Boolean)? = null, diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt index 09b6686c1e..5947eb710f 100644 --- a/kotlinx-coroutines-core/jvm/test/TestBase.kt +++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt @@ -153,7 +153,7 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { } }) - fun println(message: Any?) { + actual fun println(message: Any?) { if (disableOutCheck) kotlin.io.println(message) else previousOut.println(message) } diff --git a/kotlinx-coroutines-core/native/test/TestBase.kt b/kotlinx-coroutines-core/native/test/TestBase.kt index c25f03569a..ca876969f5 100644 --- a/kotlinx-coroutines-core/native/test/TestBase.kt +++ b/kotlinx-coroutines-core/native/test/TestBase.kt @@ -73,6 +73,10 @@ public actual open class TestBase actual constructor() { finished.value = false } + actual fun println(message: Any?) { + kotlin.io.println(message) + } + @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual fun runTest( expected: ((Throwable) -> Boolean)? = null, From 2d0bb86a1ad2b268331e89f2fc49f9815c220ec1 Mon Sep 17 00:00:00 2001 From: Nikolay Lunyak Date: Thu, 21 Sep 2023 20:00:08 +0300 Subject: [PATCH 40/53] Suppress MULTIPLE_DEFAULTS_INHERITED_FROM_SUPERTYPES warnings (#3896) K2 now detects the cases that K1 misses, and reports warnings, just in case. --- kotlinx-coroutines-core/common/src/channels/Broadcast.kt | 2 ++ kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt | 1 + kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt | 2 ++ kotlinx-coroutines-core/jvm/src/channels/Actor.kt | 1 + 4 files changed, 6 insertions(+) diff --git a/kotlinx-coroutines-core/common/src/channels/Broadcast.kt b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt index e7a58ccdc4..64b6a69595 100644 --- a/kotlinx-coroutines-core/common/src/channels/Broadcast.kt +++ b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt @@ -147,12 +147,14 @@ private open class BroadcastCoroutine( override val channel: SendChannel get() = this + @Suppress("MULTIPLE_DEFAULTS_INHERITED_FROM_SUPERTYPES_DEPRECATION_WARNING") // do not remove the MULTIPLE_DEFAULTS suppression: required in K2 @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 ?: defaultCancellationException()) return true } + @Suppress("MULTIPLE_DEFAULTS_INHERITED_FROM_SUPERTYPES_DEPRECATION_WARNING") // do not remove the MULTIPLE_DEFAULTS suppression: required in K2 final override fun cancel(cause: CancellationException?) { cancelInternal(cause ?: defaultCancellationException()) } diff --git a/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt index e3c3a30666..a967b673cc 100644 --- a/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt @@ -136,6 +136,7 @@ public class ConflatedBroadcastChannel private constructor( * * This channel is created by `BroadcastChannel(capacity)` factory function invocation. */ +@Suppress("MULTIPLE_DEFAULTS_INHERITED_FROM_SUPERTYPES_DEPRECATION_WARNING", "MULTIPLE_DEFAULTS_INHERITED_FROM_SUPERTYPES_WHEN_NO_EXPLICIT_OVERRIDE_DEPRECATION_WARNING") // do not remove the MULTIPLE_DEFAULTS suppression: required in K2 internal class BroadcastChannelImpl( /** * Buffer capacity; [Channel.CONFLATED] when this broadcast is conflated. diff --git a/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt b/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt index 3fcf388a67..7b6bd02605 100644 --- a/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt +++ b/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt @@ -21,12 +21,14 @@ internal open class ChannelCoroutine( cancelInternal(defaultCancellationException()) } + @Suppress("MULTIPLE_DEFAULTS_INHERITED_FROM_SUPERTYPES_DEPRECATION_WARNING") // do not remove the MULTIPLE_DEFAULTS suppression: required in K2 @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") final override fun cancel(cause: Throwable?): Boolean { cancelInternal(defaultCancellationException()) return true } + @Suppress("MULTIPLE_DEFAULTS_INHERITED_FROM_SUPERTYPES_DEPRECATION_WARNING") // do not remove the MULTIPLE_DEFAULTS suppression: required in K2 final override fun cancel(cause: CancellationException?) { if (isCancelled) return // Do not create an exception if the coroutine (-> the channel) is already cancelled cancelInternal(cause ?: defaultCancellationException()) diff --git a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt index e8a9152e09..6e2bef6680 100644 --- a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt +++ b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt @@ -178,6 +178,7 @@ private class LazyActorCoroutine( return super.trySend(element) } + @Suppress("MULTIPLE_DEFAULTS_INHERITED_FROM_SUPERTYPES_DEPRECATION_WARNING") // do not remove the MULTIPLE_DEFAULTS suppression: required in K2 override fun close(cause: Throwable?): Boolean { // close the channel _first_ val closed = super.close(cause) From aa5d0ce9cd13b6d554c7aa65faec908b43044eb7 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 8 Nov 2023 08:43:54 +0100 Subject: [PATCH 41/53] Fix KT-62279 by suppressing `INVISIBLE_SETTER` --- kotlinx-coroutines-debug/src/DebugProbes.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kotlinx-coroutines-debug/src/DebugProbes.kt b/kotlinx-coroutines-debug/src/DebugProbes.kt index 493b85e199..0755f48943 100644 --- a/kotlinx-coroutines-debug/src/DebugProbes.kt +++ b/kotlinx-coroutines-debug/src/DebugProbes.kt @@ -51,6 +51,7 @@ public object DebugProbes { */ public var sanitizeStackTraces: Boolean get() = DebugProbesImpl.sanitizeStackTraces + @Suppress("INVISIBLE_SETTER") // do not remove the INVISIBLE_SETTER suppression: required in k2 set(value) { DebugProbesImpl.sanitizeStackTraces = value } @@ -66,6 +67,7 @@ public object DebugProbes { */ public var enableCreationStackTraces: Boolean get() = DebugProbesImpl.enableCreationStackTraces + @Suppress("INVISIBLE_SETTER") // do not remove the INVISIBLE_SETTER suppression: required in k2 set(value) { DebugProbesImpl.enableCreationStackTraces = value } @@ -82,6 +84,7 @@ public object DebugProbes { */ public var ignoreCoroutinesWithEmptyContext: Boolean get() = DebugProbesImpl.ignoreCoroutinesWithEmptyContext + @Suppress("INVISIBLE_SETTER") // do not remove the INVISIBLE_SETTER suppression: required in k2 set(value) { DebugProbesImpl.ignoreCoroutinesWithEmptyContext = value } From addb5247b7c287fcc1c8f35d842f9ba7c32c40ea Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 19 Jul 2023 20:09:51 +0200 Subject: [PATCH 42/53] Work around KT-60304 by suppressing "INVISIBLE_SETTER" --- kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt b/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt index 84fede4946..d380bb4bd2 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt @@ -93,7 +93,8 @@ private class UnconfinedTestDispatcherImpl( override fun isDispatchNeeded(context: CoroutineContext): Boolean = false - @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // do not remove the INVISIBLE_REFERENCE suppression: required in K2 + // do not remove the INVISIBLE_REFERENCE and INVISIBLE_SETTER suppressions: required in K2 + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "INVISIBLE_SETTER") override fun dispatch(context: CoroutineContext, block: Runnable) { checkSchedulerInContext(scheduler, context) scheduler.sendDispatchEvent(context) From d5c0effcae5d7a7a4f5b11c821d3a1a261d1e8a4 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:24:19 +0300 Subject: [PATCH 43/53] Fix warnings in Native (#3957) --- .../main/kotlin/configure-compilation-conventions.gradle.kts | 2 +- kotlinx-coroutines-core/common/src/EventLoop.common.kt | 1 + kotlinx-coroutines-core/native/src/Builders.kt | 2 +- kotlinx-coroutines-core/native/src/EventLoop.kt | 3 +++ .../native/src/MultithreadedDispatchers.kt | 5 +++-- kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt | 2 ++ 6 files changed, 11 insertions(+), 4 deletions(-) diff --git a/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts b/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts index 68cbf74288..ce8e599106 100644 --- a/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts @@ -10,7 +10,7 @@ configure(subprojects) { if (name in sourceless) return@configure apply(plugin = "kotlinx-atomicfu") tasks.withType>().configureEach { - val isMainTaskName = name == "compileKotlin" || name == "compileKotlinJvm" + val isMainTaskName = name.startsWith("compileKotlin") kotlinOptions { languageVersion = getOverriddenKotlinLanguageVersion(project) apiVersion = getOverriddenKotlinApiVersion(project) diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt index 8d9eed21bc..53c03be91e 100644 --- a/kotlinx-coroutines-core/common/src/EventLoop.common.kt +++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt @@ -6,6 +6,7 @@ package kotlinx.coroutines import kotlinx.atomicfu.* import kotlinx.coroutines.internal.* +import kotlin.concurrent.Volatile import kotlin.coroutines.* import kotlin.jvm.* diff --git a/kotlinx-coroutines-core/native/src/Builders.kt b/kotlinx-coroutines-core/native/src/Builders.kt index 1f1d352dab..e3db7f16bc 100644 --- a/kotlinx-coroutines-core/native/src/Builders.kt +++ b/kotlinx-coroutines-core/native/src/Builders.kt @@ -2,7 +2,7 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:OptIn(ExperimentalContracts::class) +@file:OptIn(ExperimentalContracts::class, ObsoleteWorkersApi::class) package kotlinx.coroutines import kotlinx.cinterop.* diff --git a/kotlinx-coroutines-core/native/src/EventLoop.kt b/kotlinx-coroutines-core/native/src/EventLoop.kt index 25c3c12b78..62e51b26c7 100644 --- a/kotlinx-coroutines-core/native/src/EventLoop.kt +++ b/kotlinx-coroutines-core/native/src/EventLoop.kt @@ -2,6 +2,8 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +@file:OptIn(ObsoleteWorkersApi::class) + package kotlinx.coroutines import kotlin.coroutines.* @@ -28,4 +30,5 @@ internal class EventLoopImpl: EventLoopImplBase() { internal actual fun createEventLoop(): EventLoop = EventLoopImpl() +@Suppress("DEPRECATION") internal actual fun nanoTime(): Long = getTimeNanos() diff --git a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt index 11627ba399..548602e146 100644 --- a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt +++ b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt @@ -2,6 +2,8 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +@file:OptIn(ObsoleteWorkersApi::class) + package kotlinx.coroutines import kotlinx.atomicfu.* @@ -9,7 +11,7 @@ import kotlinx.coroutines.channels.* import kotlinx.coroutines.internal.* import kotlin.coroutines.* import kotlin.concurrent.AtomicReference -import kotlin.native.concurrent.Worker +import kotlin.native.concurrent.* import kotlin.time.* import kotlin.time.Duration.Companion.milliseconds @@ -18,7 +20,6 @@ public actual fun newFixedThreadPoolContext(nThreads: Int, name: String): Closea return MultiWorkerDispatcher(name, nThreads) } -@OptIn(ExperimentalTime::class) internal class WorkerDispatcher(name: String) : CloseableCoroutineDispatcher(), Delay { private val worker = Worker.start(name = name) diff --git a/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt index 405cbfb6a5..95069ec14d 100644 --- a/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt +++ b/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt @@ -4,6 +4,8 @@ package kotlinx.coroutines.internal +import kotlin.native.concurrent.ThreadLocal + internal actual class CommonThreadLocal(private val name: Symbol) { @Suppress("UNCHECKED_CAST") actual fun get(): T = Storage[name] as T From 9a98eabed91c71cb2da9b0fe061139632e08990b Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:26:29 +0300 Subject: [PATCH 44/53] Rollback runTest timeout to 60 seconds, configure it with getenv (#3945) This commit attempts to fix #3800. The first part of the fix, reverting the timeout to 60 seconds, is successful. The second part, allowing global configuration, is only provided for the JVM so far. On JVM, Native, and Node JS, it's possible to use environment variables to communicate data to the process, and it could in theory be the solution, but it doesn't seem to interoperate well with Gradle. The best attempt so far was to use this: ```kotlin tasks.withType(AbstractTestTask::class).all { if (this is ProcessForkOptions) { environment("kotlinx.coroutines.test.default_timeout", "1ms") } } ``` Unfortunately, only `jvmTest` implements `ProcessForkOptions`. Without a clear way to configure the `runTest` timeout in Gradle builds for all targets, we only support this via system properties on the JVM until a better mechanism appears. --- .../common/src/TestBuilders.kt | 35 +++++++++++++++---- .../js/src/TestBuilders.kt | 2 ++ .../jvm/src/TestBuildersJvm.kt | 7 ++++ .../native/src/TestBuilders.kt | 3 +- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/kotlinx-coroutines-test/common/src/TestBuilders.kt b/kotlinx-coroutines-test/common/src/TestBuilders.kt index f95dabc3d7..26952efa6d 100644 --- a/kotlinx-coroutines-test/common/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/common/src/TestBuilders.kt @@ -14,7 +14,6 @@ import kotlin.jvm.* import kotlin.time.* import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds -import kotlinx.coroutines.internal.* /** * A test result. @@ -122,8 +121,14 @@ public expect class TestResult * * #### Timing out * - * There's a built-in timeout of 10 seconds for the test body. If the test body doesn't complete within this time, - * then the test fails with an [AssertionError]. The timeout can be changed by setting the [timeout] parameter. + * There's a built-in timeout of 60 seconds for the test body. If the test body doesn't complete within this time, + * then the test fails with an [AssertionError]. The timeout can be changed for each test separately by setting the + * [timeout] parameter. + * + * Additionally, setting the `kotlinx.coroutines.test.default_timeout` system property on the + * JVM to any string that can be parsed using [Duration.parse] (like `1m`, `30s` or `1500ms`) will change the default + * timeout to that value for all tests whose [timeout] is not set explicitly; setting it to anything else will throw an + * exception every time [runTest] is invoked. * * On timeout, the test body is cancelled so that the test finishes. If the code inside the test body does not * respond to cancellation, the timeout will not be able to make the test execution stop. @@ -157,7 +162,7 @@ public expect class TestResult */ public fun runTest( context: CoroutineContext = EmptyCoroutineContext, - timeout: Duration = DEFAULT_TIMEOUT, + timeout: Duration = DEFAULT_TIMEOUT.getOrThrow(), testBody: suspend TestScope.() -> Unit ): TestResult { check(context[RunningInRunTest] == null) { @@ -301,7 +306,7 @@ public fun runTest( * Performs [runTest] on an existing [TestScope]. See the documentation for [runTest] for details. */ public fun TestScope.runTest( - timeout: Duration = DEFAULT_TIMEOUT, + timeout: Duration = DEFAULT_TIMEOUT.getOrThrow(), testBody: suspend TestScope.() -> Unit ): TestResult = asSpecificImplementation().let { scope -> scope.enter() @@ -421,8 +426,15 @@ internal const val DEFAULT_DISPATCH_TIMEOUT_MS = 60_000L /** * The default timeout to use when running a test. + * + * It's not just a [Duration] but a [Result] so that every access to [runTest] + * throws the same clear exception if parsing the environment variable failed. + * Otherwise, the parsing error would only be thrown in one tests, while the + * other ones would get an incomprehensible `NoClassDefFoundError`. */ -internal val DEFAULT_TIMEOUT = 10.seconds +private val DEFAULT_TIMEOUT: Result = runCatching { + systemProperty("kotlinx.coroutines.test.default_timeout", Duration::parse, 60.seconds) +} /** * Run the [body][testBody] of the [test coroutine][coroutine], waiting for asynchronous completions for at most @@ -571,6 +583,17 @@ internal fun throwAll(head: Throwable?, other: List) { internal expect fun dumpCoroutines() +private fun systemProperty( + name: String, + parse: (String) -> T, + default: T, +): T { + val value = systemPropertyImpl(name) ?: return default + return parse(value) +} + +internal expect fun systemPropertyImpl(name: String): String? + @Deprecated( "This is for binary compatibility with the `runTest` overload that existed at some point", level = DeprecationLevel.HIDDEN diff --git a/kotlinx-coroutines-test/js/src/TestBuilders.kt b/kotlinx-coroutines-test/js/src/TestBuilders.kt index 97c9da0eee..f1148135d9 100644 --- a/kotlinx-coroutines-test/js/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/js/src/TestBuilders.kt @@ -9,6 +9,8 @@ import kotlin.js.* @Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_TO_CLASS_WITH_DECLARATION_SITE_VARIANCE") public actual typealias TestResult = Promise +internal actual fun systemPropertyImpl(name: String): String? = null + internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() -> Unit): TestResult = GlobalScope.promise { testProcedure() diff --git a/kotlinx-coroutines-test/jvm/src/TestBuildersJvm.kt b/kotlinx-coroutines-test/jvm/src/TestBuildersJvm.kt index 0521fd22ae..1307e036ba 100644 --- a/kotlinx-coroutines-test/jvm/src/TestBuildersJvm.kt +++ b/kotlinx-coroutines-test/jvm/src/TestBuildersJvm.kt @@ -15,6 +15,13 @@ internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() -> } } +internal actual fun systemPropertyImpl(name: String): String? = + try { + System.getProperty(name) + } catch (e: SecurityException) { + null + } + internal actual fun dumpCoroutines() { @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") if (DebugProbesImpl.isInstalled) { diff --git a/kotlinx-coroutines-test/native/src/TestBuilders.kt b/kotlinx-coroutines-test/native/src/TestBuilders.kt index 607dec6a73..2714b45823 100644 --- a/kotlinx-coroutines-test/native/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/native/src/TestBuilders.kt @@ -4,7 +4,6 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* -import kotlin.native.concurrent.* @Suppress("ACTUAL_WITHOUT_EXPECT") public actual typealias TestResult = Unit @@ -15,4 +14,6 @@ internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() -> } } +internal actual fun systemPropertyImpl(name: String): String? = null + internal actual fun dumpCoroutines() { } From 00a0767263aaf0debca4444a9251e402c63ddb7e Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 30 Nov 2023 16:28:38 +0100 Subject: [PATCH 45/53] Explain SafeCollector hackery (#3811) Co-authored-by: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> --- .../src/flow/internal/SafeCollector.common.kt | 2 + .../jvm/src/flow/internal/SafeCollector.kt | 42 ++++++++++++++++--- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.common.kt b/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.common.kt index 723a322be3..68d34f264c 100644 --- a/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.common.kt +++ b/kotlinx-coroutines-core/common/src/flow/internal/SafeCollector.common.kt @@ -10,6 +10,8 @@ import kotlinx.coroutines.internal.ScopeCoroutine import kotlin.coroutines.* import kotlin.jvm.* +// Collector that ensures exception transparency and context preservation on a best-effort basis. +// See an explanation in SafeCollector JVM actualization. internal expect class SafeCollector( collector: FlowCollector, collectContext: CoroutineContext diff --git a/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt b/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt index 6bfd4ce847..04ffed9717 100644 --- a/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt +++ b/kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt @@ -13,9 +13,25 @@ import kotlin.coroutines.jvm.internal.* @Suppress("UNCHECKED_CAST") private val emitFun = FlowCollector::emit as Function3, Any?, Continuation, Any?> -/* - * Implementor of ContinuationImpl (that will be preserved as ABI nearly forever) - * in order to properly control 'intercepted()' lifecycle. + +/** + * A safe collector is an instance of [FlowCollector] that ensures that neither context preservation + * nor exception transparency invariants are broken. Instances of [SafeCollector] are used in flow + * operators that provide raw access to the [FlowCollector] e.g. [Flow.transform]. + * Mechanically, each [emit] call captures [currentCoroutineContext], ensures it is not different from the + * previously caught one and proceeds further. If an exception is thrown from the downstream, + * it is caught, and any further attempts to [emit] lead to the [IllegalStateException]. + * + * ### Performance hacks + * + * Implementor of [ContinuationImpl] (that will be preserved as ABI nearly forever) + * in order to properly control `intercepted()` lifecycle. + * The safe collector implements [ContinuationImpl] to pretend it *is* a state-machine of its own `emit` method. + * It is [ContinuationImpl] and not any other [Continuation] subclass because only [ContinuationImpl] supports `intercepted()` caching. + * This is the most performance-sensitive place in the overall flow pipeline, because otherwise safe collector is forced to allocate + * a state machine on each element being emitted for each intermediate stage where the safe collector is present. + * + * See a comment to [emit] for the explanation of what and how is being optimized. */ @Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "UNCHECKED_CAST") internal actual class SafeCollector actual constructor( @@ -56,11 +72,15 @@ internal actual class SafeCollector actual constructor( /** * This is a crafty implementation of state-machine reusing. - * First it checks that it is not used concurrently (which we explicitly prohibit) and - * then just cache an instance of the completion_ in order to avoid extra allocation on each emit, + * + * First it checks that it is not used concurrently (which we explicitly prohibit), and + * then just caches an instance of the completion_ in order to avoid extra allocation on each emit, * making it effectively garbage-free on its hot-path. + * + * See `emit` overload. */ actual override suspend fun emit(value: T) { + // NB: it is a tail-call, so we are sure `uCont` is the completion of the emit's **caller**. return suspendCoroutineUninterceptedOrReturn sc@{ uCont -> try { emit(uCont, value) @@ -74,10 +94,20 @@ internal actual class SafeCollector actual constructor( } } + /** + * Here we use the following trick: + * - Perform all the required checks + * - Having a non-intercepted, non-cancellable caller's `uCont`, we leverage our implementation knowledge + * and invoke `collector.emit(T)` as `collector.emit(value: T, completion: Continuation), passing `this` + * as the completion. We also setup `this` state, so if the `completion.resume` is invoked, we are + * invoking `uCont.resume` properly in accordance with `ContinuationImpl`/`BaseContinuationImpl` internal invariants. + * + * Note that in such scenarios, `collector.emit` completion is the current instance of SafeCollector and thus is reused. + */ private fun emit(uCont: Continuation, value: T): Any? { val currentContext = uCont.context currentContext.ensureActive() - // This check is triggered once per flow on happy path. + // This check is triggered once per flow on a happy path. val previousContext = lastEmissionContext if (previousContext !== currentContext) { checkContext(currentContext, previousContext, value) From a79db3740b24591c5e373b485be5abc59bcfdb87 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 30 Nov 2023 16:30:13 +0100 Subject: [PATCH 46/53] =?UTF-8?q?Create=20Dispatcher.Default=20threads=20w?= =?UTF-8?q?ith=20the=20same=20context=20classloader=20a=E2=80=A6=20(#3877)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create Dispatcher.Default threads with the same context classloader as the dispatcher itself In order to properly operate in modularized on a classloader level environments with the absence of other workarounds (i.e. supplying application-specific thread factory) * Do the same trick for DefaultExecutor Fixes #3832 --- kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt | 7 +++++++ .../jvm/src/scheduling/CoroutineScheduler.kt | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt index 4d23aff3f7..3229502b32 100644 --- a/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt +++ b/kotlinx-coroutines-core/jvm/src/DefaultExecutor.kt @@ -135,6 +135,13 @@ internal actual object DefaultExecutor : EventLoopImplBase(), Runnable { private fun createThreadSync(): Thread { return _thread ?: Thread(this, THREAD_NAME).apply { _thread = this + /* + * `DefaultExecutor` is a global singleton that creates its thread lazily. + * To isolate the classloaders properly, we are inherting the context classloader from + * the singleton itself instead of using parent' thread one + * in order not to accidentally capture temporary application classloader. + */ + contextClassLoader = this@DefaultExecutor.javaClass.classLoader isDaemon = true start() } diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt index 5ca3de5726..84fa474a3d 100644 --- a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt +++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt @@ -593,6 +593,13 @@ internal class CoroutineScheduler( internal inner class Worker private constructor() : Thread() { init { isDaemon = true + /* + * `Dispatchers.Default` is used as *the* dispatcher in the containerized environments, + * isolated by their own classloaders. Workers are populated lazily, thus we are inheriting + * `Dispatchers.Default` context class loader here instead of using parent' thread one + * in order not to accidentally capture temporary application classloader. + */ + contextClassLoader = this@CoroutineScheduler.javaClass.classLoader } // guarded by scheduler lock, index in workers array, 0 when not in array (terminated) From 5cd845b639ed04c99b4c027a6f8c66b722ef6e4a Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 30 Nov 2023 23:45:52 +0100 Subject: [PATCH 47/53] =?UTF-8?q?Fix=20warnings=20that=20manifested=20them?= =?UTF-8?q?selves=20as=20errors=20after=20multiple=20simu=E2=80=A6=20(#395?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix warnings that manifested themselves as errors after multiple simultaneous merges --- kotlinx-coroutines-core/build.gradle | 4 +++- kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt | 2 ++ kotlinx-coroutines-core/nativeDarwin/src/WorkerMain.kt | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index 7c26e9d280..c8d1c3c197 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -374,7 +374,9 @@ koverReport { "kotlinx.coroutines.debug.*", // Tested by debug module "kotlinx.coroutines.channels.ChannelsKt__DeprecatedKt.*", // Deprecated "kotlinx.coroutines.scheduling.LimitingDispatcher", // Deprecated - "kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher" // Deprecated + "kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher", // Deprecated + "_COROUTINE._CREATION", // For IDE navigation + "_COROUTINE._BOUNDARY", // For IDE navigation ) } } diff --git a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt index 68d19bf685..a39139cf91 100644 --- a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt @@ -2,6 +2,8 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +@file:OptIn(BetaInteropApi::class) + package kotlinx.coroutines import kotlinx.cinterop.* diff --git a/kotlinx-coroutines-core/nativeDarwin/src/WorkerMain.kt b/kotlinx-coroutines-core/nativeDarwin/src/WorkerMain.kt index 010bd03089..b2a58f5d77 100644 --- a/kotlinx-coroutines-core/nativeDarwin/src/WorkerMain.kt +++ b/kotlinx-coroutines-core/nativeDarwin/src/WorkerMain.kt @@ -6,6 +6,7 @@ package kotlinx.coroutines import kotlinx.cinterop.* +@OptIn(BetaInteropApi::class) internal actual inline fun workerMain(block: () -> Unit) { autoreleasepool { block() From 3ceb35d017c1029e4417de4dcc78e341bc3ee923 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:34:56 +0300 Subject: [PATCH 48/53] Fix kotlinx-coroutines-debug having the wrong module-info (#3948) Also, rewrite some build logic to gradle.kts --- build.gradle | 35 +----- buildSrc/src/main/kotlin/VersionFile.kt | 29 +++++ .../version-file-conventions.gradle.kts | 17 +++ gradle/publish.gradle | 6 - kotlinx-coroutines-debug/build.gradle | 73 ----------- kotlinx-coroutines-debug/build.gradle.kts | 113 ++++++++++++++++++ 6 files changed, 160 insertions(+), 113 deletions(-) create mode 100644 buildSrc/src/main/kotlin/VersionFile.kt create mode 100644 buildSrc/src/main/kotlin/version-file-conventions.gradle.kts delete mode 100644 kotlinx-coroutines-debug/build.gradle create mode 100644 kotlinx-coroutines-debug/build.gradle.kts diff --git a/build.gradle b/build.gradle index 2b00f602e3..f6c4cf31c7 100644 --- a/build.gradle +++ b/build.gradle @@ -177,6 +177,7 @@ configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != core apply plugin: "bom-conventions" apply plugin: "java-modularity-conventions" +apply plugin: "version-file-conventions" if (build_snapshot_train) { println "Hacking test tasks, removing stress and flaky tests" @@ -252,40 +253,6 @@ configure(subprojects.findAll { !unpublished.contains(it.name) }) { } } } - - def thisProject = it - if (thisProject.name in sourceless) { - return - } - - def versionFileTask = thisProject.tasks.register("versionFileTask") { - def name = thisProject.name.replace("-", "_") - def versionFile = thisProject.layout.buildDirectory.file("${name}.version") - it.outputs.file(versionFile) - - it.doLast { - versionFile.get().asFile.text = version.toString() - } - } - - List jarTasks - if (isMultiplatform(it)) { - jarTasks = ["jvmJar"] - } else if (it.name == "kotlinx-coroutines-debug") { - // We shadow debug module instead of just packaging it - jarTasks = ["shadowJar"] - } else { - jarTasks = ["jar"] - } - - for (name in jarTasks) { - thisProject.tasks.named(name, Jar) { - it.dependsOn versionFileTask - it.from(versionFileTask) { - into("META-INF") - } - } - } } // Report Kotlin compiler version when building project diff --git a/buildSrc/src/main/kotlin/VersionFile.kt b/buildSrc/src/main/kotlin/VersionFile.kt new file mode 100644 index 0000000000..ef0ef0faa1 --- /dev/null +++ b/buildSrc/src/main/kotlin/VersionFile.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +import org.gradle.api.* +import org.gradle.api.tasks.* + +/** + * Adds 'module_name.version' file to the project's JAR META-INF + * for the better toolability. See #2941 + */ +object VersionFile { + fun registerVersionFileTask(project: Project): TaskProvider { + val versionFile = project.layout.buildDirectory.file("${project.name.replace('-', '_')}.version") + val version = project.version.toString() + return project.tasks.register("versionFileTask") { + outputs.file(versionFile) + doLast { + versionFile.get().asFile.writeText(version) + } + } + } + + fun fromVersionFile(target: AbstractCopyTask, versionFileTask: TaskProvider) { + target.from(versionFileTask) { + into("META-INF") + } + } +} diff --git a/buildSrc/src/main/kotlin/version-file-conventions.gradle.kts b/buildSrc/src/main/kotlin/version-file-conventions.gradle.kts new file mode 100644 index 0000000000..587e184b30 --- /dev/null +++ b/buildSrc/src/main/kotlin/version-file-conventions.gradle.kts @@ -0,0 +1,17 @@ +import org.gradle.api.tasks.bundling.* + +configure(subprojects.filter { !unpublished.contains(it.name) && it.name !in sourceless }) { + val project = this + val jarTaskName = when { + project.name == "kotlinx-coroutines-debug" -> { + project.apply(plugin = "com.github.johnrengelman.shadow") + "shadowJar" + } + isMultiplatform -> "jvmJar" + else -> "jar" + } + val versionFileTask = VersionFile.registerVersionFileTask(project) + tasks.withType(Jar::class.java).named(jarTaskName) { + VersionFile.fromVersionFile(this, versionFileTask) + } +} diff --git a/gradle/publish.gradle b/gradle/publish.gradle index f3b1561dde..9984ae8e70 100644 --- a/gradle/publish.gradle +++ b/gradle/publish.gradle @@ -2,8 +2,6 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -import org.gradle.util.VersionNumber - // Configures publishing of Maven artifacts to Maven Central apply plugin: 'maven-publish' @@ -15,10 +13,6 @@ def isMultiplatform = project.name == "kotlinx-coroutines-core" || project.name def isBom = project.name == "kotlinx-coroutines-bom" if (!isBom) { - if (project.name == "kotlinx-coroutines-debug") { - apply plugin: "com.github.johnrengelman.shadow" - } - // empty xxx-javadoc.jar task javadocJar(type: Jar) { archiveClassifier = 'javadoc' diff --git a/kotlinx-coroutines-debug/build.gradle b/kotlinx-coroutines-debug/build.gradle deleted file mode 100644 index 42d0b8d0f0..0000000000 --- a/kotlinx-coroutines-debug/build.gradle +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -apply plugin: "com.github.johnrengelman.shadow" - -// apply plugin to use autocomplete for Kover DSL -apply plugin: 'org.jetbrains.kotlinx.kover' - -configurations { - shadowDeps // shaded dependencies, not included into the resulting .pom file - compileOnly.extendsFrom(shadowDeps) - runtimeOnly.extendsFrom(shadowDeps) -} - -dependencies { - compileOnly "junit:junit:$junit_version" - compileOnly "org.junit.jupiter:junit-jupiter-api:$junit5_version" - testImplementation "org.junit.jupiter:junit-jupiter-engine:$junit5_version" - testImplementation "org.junit.platform:junit-platform-testkit:1.7.0" - shadowDeps "net.bytebuddy:byte-buddy:$byte_buddy_version" - shadowDeps "net.bytebuddy:byte-buddy-agent:$byte_buddy_version" - compileOnly "io.projectreactor.tools:blockhound:$blockhound_version" - testImplementation "io.projectreactor.tools:blockhound:$blockhound_version" - testImplementation "com.google.code.gson:gson:2.8.6" - api "net.java.dev.jna:jna:$jna_version" - api "net.java.dev.jna:jna-platform:$jna_version" -} - -java { - /* This is needed to be able to run JUnit5 tests. Otherwise, Gradle complains that it can't find the - JVM1.6-compatible version of the `junit-jupiter-api` artifact. */ - disableAutoTargetJvm() -} - -// This is required for BlockHound tests to work, see https://github.com/Kotlin/kotlinx.coroutines/issues/3701 -tasks.withType(Test).configureEach { - if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_13)) { - jvmArgs += ["-XX:+AllowRedefinitionToAddDeleteMethods"] - } -} - -jar { - setEnabled(false) -} - -def shadowJarTask = shadowJar { - classifier null - // Shadow only byte buddy, do not package kotlin stdlib - configurations = [project.configurations.shadowDeps] - relocate('net.bytebuddy', 'kotlinx.coroutines.repackaged.net.bytebuddy') - - manifest { - attributes "Premain-Class": "kotlinx.coroutines.debug.AgentPremain" - attributes "Can-Redefine-Classes": "true" - } -} - -configurations { - artifacts { - add("apiElements", shadowJarTask) - add("runtimeElements", shadowJarTask) - } -} - -koverReport { - filters { - excludes { - // Never used, safety mechanism - classes("kotlinx.coroutines.debug.internal.NoOpProbesKt") - } - } -} diff --git a/kotlinx-coroutines-debug/build.gradle.kts b/kotlinx-coroutines-debug/build.gradle.kts new file mode 100644 index 0000000000..ed8b918b8a --- /dev/null +++ b/kotlinx-coroutines-debug/build.gradle.kts @@ -0,0 +1,113 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +import com.github.jengelman.gradle.plugins.shadow.tasks.* +import java.net.* +import java.nio.file.* + +plugins { + id("com.github.johnrengelman.shadow") + id("org.jetbrains.kotlinx.kover") // apply plugin to use autocomplete for Kover DSL +} + +configurations { + val shadowDeps by creating + compileOnly.configure { + extendsFrom(shadowDeps) + } + runtimeOnly.configure { + extendsFrom(shadowDeps) + } +} + +val junit_version by properties +val junit5_version by properties +val byte_buddy_version by properties +val blockhound_version by properties +val jna_version by properties + +dependencies { + compileOnly("junit:junit:$junit_version") + compileOnly("org.junit.jupiter:junit-jupiter-api:$junit5_version") + testImplementation("org.junit.jupiter:junit-jupiter-engine:$junit5_version") + testImplementation("org.junit.platform:junit-platform-testkit:1.7.0") + add("shadowDeps", "net.bytebuddy:byte-buddy:$byte_buddy_version") + add("shadowDeps", "net.bytebuddy:byte-buddy-agent:$byte_buddy_version") + compileOnly("io.projectreactor.tools:blockhound:$blockhound_version") + testImplementation("io.projectreactor.tools:blockhound:$blockhound_version") + testImplementation("com.google.code.gson:gson:2.8.6") + api("net.java.dev.jna:jna:$jna_version") + api("net.java.dev.jna:jna-platform:$jna_version") +} + +java { + /* This is needed to be able to run JUnit5 tests. Otherwise, Gradle complains that it can't find the + JVM1.6-compatible version of the `junit-jupiter-api` artifact. */ + disableAutoTargetJvm() +} + +// This is required for BlockHound tests to work, see https://github.com/Kotlin/kotlinx.coroutines/issues/3701 +tasks.withType().configureEach { + if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_13)) { + jvmArgs("-XX:+AllowRedefinitionToAddDeleteMethods") + } +} + +val jar by tasks.existing(Jar::class) { + enabled = false +} + +val shadowJar by tasks.existing(ShadowJar::class) { + // Shadow only byte buddy, do not package kotlin stdlib + configurations = listOf(project.configurations["shadowDeps"]) + relocate("net.bytebuddy", "kotlinx.coroutines.repackaged.net.bytebuddy") + /* These classifiers are both set to `null` to trick Gradle into thinking that this jar file is both the + artifact from the `jar` task and the one from `shadowJar`. Without this, Gradle complains that the artifact + from the `jar` task is not present when the compilaton finishes, even if the file with this name exists. */ + archiveClassifier.convention(null as String?) + archiveClassifier.set(null as String?) + archiveBaseName.set(jar.flatMap { it.archiveBaseName }) + archiveVersion.set(jar.flatMap { it.archiveVersion }) + manifest { + attributes( + mapOf( + "Premain-Class" to "kotlinx.coroutines.debug.AgentPremain", + "Can-Redefine-Classes" to "true", + "Multi-Release" to "true" + ) + ) + } + // add module-info.class to the META-INF/versions/9/ directory. + dependsOn(tasks.compileModuleInfoJava) + doLast { + // We can't do that directly with the shadowJar task because it doesn't support replacing existing files. + val zipPath = this@existing.outputs.files.singleFile.toPath() + val zipUri = URI.create("jar:${zipPath.toUri()}") + val moduleInfoFilePath = tasks.compileModuleInfoJava.get().outputs.files.asFileTree.matching { + include("module-info.class") + }.singleFile.toPath() + FileSystems.newFileSystem(zipUri, emptyMap()).use { fs -> + val moduleInfoFile = fs.getPath("META-INF/versions/9/module-info.class") + Files.copy(moduleInfoFilePath, moduleInfoFile, StandardCopyOption.REPLACE_EXISTING) + } + } +} + +configurations { + // shadowJar is already part of the `shadowRuntimeElements` and `shadowApiElements`, but the other subprojects + // that depend on `kotlinx-coroutines-debug` look at `runtimeElements` and `apiElements`. + artifacts { + add("apiElements", shadowJar) + add("runtimeElements", shadowJar) + } +} + +koverReport { + filters { + excludes { + // Never used, safety mechanism + classes("kotlinx.coroutines.debug.internal.NoOpProbesKt") + } + } +} From 7f32340a9a62cbab999d76429f64c594c021b841 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Fri, 1 Dec 2023 15:44:02 +0300 Subject: [PATCH 49/53] Ensure Dispatchers.Main != Dispatchers.Main.immediate on Android (#3924) * Simplify some code * Unify and generalize the tests for all Main dispatchers * Cleanup build configuration in JavaFx * Allow using kotlin.test from tests in the core module from JavaFx Fixes #3545 --- .../common/test/MainDispatcherTestBase.kt | 266 ++++++++++++++++++ .../js/test/ImmediateDispatcherTest.kt | 11 +- .../nativeDarwin/src/Dispatchers.kt | 2 +- .../nativeDarwin/test/MainDispatcherTest.kt | 120 +------- .../src/HandlerDispatcher.kt | 13 +- .../test/HandlerDispatcherTest.kt | 43 ++- ui/kotlinx-coroutines-javafx/build.gradle.kts | 11 +- .../test/JavaFxDispatcherTest.kt | 53 +--- ui/kotlinx-coroutines-swing/test/SwingTest.kt | 91 +----- 9 files changed, 336 insertions(+), 274 deletions(-) create mode 100644 kotlinx-coroutines-core/common/test/MainDispatcherTestBase.kt diff --git a/kotlinx-coroutines-core/common/test/MainDispatcherTestBase.kt b/kotlinx-coroutines-core/common/test/MainDispatcherTestBase.kt new file mode 100644 index 0000000000..baf56121f8 --- /dev/null +++ b/kotlinx-coroutines-core/common/test/MainDispatcherTestBase.kt @@ -0,0 +1,266 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlin.test.* + +abstract class MainDispatcherTestBase: TestBase() { + + open fun shouldSkipTesting(): Boolean = false + + open suspend fun spinTest(testBody: Job) { + testBody.join() + } + + abstract fun isMainThread(): Boolean? + + /** Runs the given block as a test, unless [shouldSkipTesting] indicates that the environment is not suitable. */ + fun runTestOrSkip(block: suspend CoroutineScope.() -> Unit): TestResult { + // written as a block body to make the need to return `TestResult` explicit + return runTest { + if (shouldSkipTesting()) return@runTest + val testBody = launch(Dispatchers.Default) { + block() + } + spinTest(testBody) + } + } + + /** Tests the [toString] behavior of [Dispatchers.Main] and [MainCoroutineDispatcher.immediate] */ + @Test + fun testMainDispatcherToString() { + assertEquals("Dispatchers.Main", Dispatchers.Main.toString()) + assertEquals("Dispatchers.Main.immediate", Dispatchers.Main.immediate.toString()) + } + + /** Tests that the tasks scheduled earlier from [MainCoroutineDispatcher.immediate] will be executed earlier, + * even if the immediate dispatcher was entered from the main thread. */ + @Test + fun testMainDispatcherOrderingInMainThread() = runTestOrSkip { + withContext(Dispatchers.Main) { + testMainDispatcherOrdering() + } + } + + /** Tests that the tasks scheduled earlier from [MainCoroutineDispatcher.immediate] will be executed earlier + * if the immediate dispatcher was entered from outside the main thread. */ + @Test + fun testMainDispatcherOrderingOutsideMainThread() = runTestOrSkip { + testMainDispatcherOrdering() + } + + /** Tests that [Dispatchers.Main] and its [MainCoroutineDispatcher.immediate] are treated as different values. */ + @Test + fun testHandlerDispatcherNotEqualToImmediate() { + assertNotEquals(Dispatchers.Main, Dispatchers.Main.immediate) + } + + /** Tests that [Dispatchers.Main] shares its queue with [MainCoroutineDispatcher.immediate]. */ + @Test + fun testImmediateDispatcherYield() = runTestOrSkip { + withContext(Dispatchers.Main) { + expect(1) + checkIsMainThread() + // launch in the immediate dispatcher + launch(Dispatchers.Main.immediate) { + expect(2) + yield() + expect(4) + } + expect(3) // after yield + yield() // yield back + expect(5) + } + finish(6) + } + + /** Tests that entering [MainCoroutineDispatcher.immediate] from [Dispatchers.Main] happens immediately. */ + @Test + fun testEnteringImmediateFromMain() = runTestOrSkip { + withContext(Dispatchers.Main) { + expect(1) + val job = launch { expect(3) } + withContext(Dispatchers.Main.immediate) { + expect(2) + } + job.join() + } + finish(4) + } + + /** Tests that dispatching to [MainCoroutineDispatcher.immediate] is required from and only from dispatchers + * other than the main dispatchers and that it's always required for [Dispatchers.Main] itself. */ + @Test + fun testDispatchRequirements() = runTestOrSkip { + checkDispatchRequirements() + withContext(Dispatchers.Main) { + checkDispatchRequirements() + withContext(Dispatchers.Main.immediate) { + checkDispatchRequirements() + } + checkDispatchRequirements() + } + checkDispatchRequirements() + } + + private suspend fun checkDispatchRequirements() { + isMainThread()?.let { assertNotEquals(it, Dispatchers.Main.immediate.isDispatchNeeded(currentCoroutineContext())) } + assertTrue(Dispatchers.Main.isDispatchNeeded(currentCoroutineContext())) + assertTrue(Dispatchers.Default.isDispatchNeeded(currentCoroutineContext())) + } + + /** Tests that launching a coroutine in [MainScope] will execute it in the main thread. */ + @Test + fun testLaunchInMainScope() = runTestOrSkip { + var executed = false + withMainScope { + launch { + checkIsMainThread() + executed = true + }.join() + if (!executed) throw AssertionError("Should be executed") + } + } + + /** Tests that a failure in [MainScope] will not propagate upwards. */ + @Test + fun testFailureInMainScope() = runTestOrSkip { + var exception: Throwable? = null + withMainScope { + launch(CoroutineExceptionHandler { ctx, e -> exception = e }) { + checkIsMainThread() + throw TestException() + }.join() + } + if (exception!! !is TestException) throw AssertionError("Expected TestException, but had $exception") + } + + /** Tests cancellation in [MainScope]. */ + @Test + fun testCancellationInMainScope() = runTestOrSkip { + withMainScope { + cancel() + launch(start = CoroutineStart.ATOMIC) { + checkIsMainThread() + delay(Long.MAX_VALUE) + }.join() + } + } + + private suspend fun withMainScope(block: suspend CoroutineScope.() -> R): R { + MainScope().apply { + return block().also { coroutineContext[Job]!!.cancelAndJoin() } + } + } + + private suspend fun testMainDispatcherOrdering() { + withContext(Dispatchers.Main.immediate) { + expect(1) + launch(Dispatchers.Main) { + expect(2) + } + withContext(Dispatchers.Main) { + finish(3) + } + } + } + + abstract class WithRealTimeDelay : MainDispatcherTestBase() { + abstract fun scheduleOnMainQueue(block: () -> Unit) + + /** Tests that after a delay, the execution gets back to the main thread. */ + @Test + fun testDelay() = runTestOrSkip { + expect(1) + checkNotMainThread() + scheduleOnMainQueue { expect(2) } + withContext(Dispatchers.Main) { + checkIsMainThread() + expect(3) + scheduleOnMainQueue { expect(4) } + delay(100) + checkIsMainThread() + expect(5) + } + checkNotMainThread() + finish(6) + } + + /** Tests that [Dispatchers.Main] is in agreement with the default time source: it's not much slower. */ + @Test + fun testWithTimeoutContextDelayNoTimeout() = runTestOrSkip { + expect(1) + withTimeout(1000) { + withContext(Dispatchers.Main) { + checkIsMainThread() + expect(2) + delay(100) + checkIsMainThread() + expect(3) + } + } + checkNotMainThread() + finish(4) + } + + /** Tests that [Dispatchers.Main] is in agreement with the default time source: it's not much faster. */ + @Test + fun testWithTimeoutContextDelayTimeout() = runTestOrSkip { + expect(1) + assertFailsWith { + withTimeout(300) { + withContext(Dispatchers.Main) { + checkIsMainThread() + expect(2) + delay(1000) + expectUnreached() + } + } + expectUnreached() + } + checkNotMainThread() + finish(3) + } + + /** Tests that the timeout of [Dispatchers.Main] is in agreement with its [delay]: it's not much faster. */ + @Test + fun testWithContextTimeoutDelayNoTimeout() = runTestOrSkip { + expect(1) + withContext(Dispatchers.Main) { + withTimeout(1000) { + checkIsMainThread() + expect(2) + delay(100) + checkIsMainThread() + expect(3) + } + } + checkNotMainThread() + finish(4) + } + + /** Tests that the timeout of [Dispatchers.Main] is in agreement with its [delay]: it's not much slower. */ + @Test + fun testWithContextTimeoutDelayTimeout() = runTestOrSkip { + expect(1) + assertFailsWith { + withContext(Dispatchers.Main) { + withTimeout(100) { + checkIsMainThread() + expect(2) + delay(1000) + expectUnreached() + } + } + expectUnreached() + } + checkNotMainThread() + finish(3) + } + } + + fun checkIsMainThread() { isMainThread()?.let { check(it) } } + fun checkNotMainThread() { isMainThread()?.let { check(!it) } } +} diff --git a/kotlinx-coroutines-core/js/test/ImmediateDispatcherTest.kt b/kotlinx-coroutines-core/js/test/ImmediateDispatcherTest.kt index 7ca6a242b2..09b96eb6ec 100644 --- a/kotlinx-coroutines-core/js/test/ImmediateDispatcherTest.kt +++ b/kotlinx-coroutines-core/js/test/ImmediateDispatcherTest.kt @@ -4,14 +4,17 @@ package kotlinx.coroutines +import kotlin.coroutines.* import kotlin.test.* -class ImmediateDispatcherTest : TestBase() { +class ImmediateDispatcherTest : MainDispatcherTestBase.WithRealTimeDelay() { + /** Tests that [MainCoroutineDispatcher.immediate] doesn't require dispatches from the test context. */ @Test fun testImmediate() = runTest { expect(1) val job = launch { expect(3) } + assertFalse(Dispatchers.Main.immediate.isDispatchNeeded(currentCoroutineContext())) withContext(Dispatchers.Main.immediate) { expect(2) } @@ -29,4 +32,10 @@ class ImmediateDispatcherTest : TestBase() { job.join() finish(4) } + + override fun isMainThread(): Boolean? = null + + override fun scheduleOnMainQueue(block: () -> Unit) { + Dispatchers.Default.dispatch(EmptyCoroutineContext, Runnable { block() }) + } } diff --git a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt index a39139cf91..bab9c9094e 100644 --- a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt @@ -68,7 +68,7 @@ private class DarwinMainDispatcher( } override fun toString(): String = - "MainDispatcher${ if(invokeImmediately) "[immediate]" else "" }" + if (invokeImmediately) "Dispatchers.Main.immediate" else "Dispatchers.Main" } private typealias TimerBlock = (CFRunLoopTimerRef?) -> Unit diff --git a/kotlinx-coroutines-core/nativeDarwin/test/MainDispatcherTest.kt b/kotlinx-coroutines-core/nativeDarwin/test/MainDispatcherTest.kt index 9904f06c5f..ba5c29cec2 100644 --- a/kotlinx-coroutines-core/nativeDarwin/test/MainDispatcherTest.kt +++ b/kotlinx-coroutines-core/nativeDarwin/test/MainDispatcherTest.kt @@ -4,126 +4,24 @@ package kotlinx.coroutines +import kotlinx.cinterop.* import platform.CoreFoundation.* import platform.darwin.* import kotlin.coroutines.* import kotlin.test.* -class MainDispatcherTest : TestBase() { +class MainDispatcherTest : MainDispatcherTestBase.WithRealTimeDelay() { - private fun isMainThread(): Boolean = CFRunLoopGetCurrent() == CFRunLoopGetMain() - private fun canTestMainDispatcher() = !isMainThread() + override fun isMainThread(): Boolean = CFRunLoopGetCurrent() == CFRunLoopGetMain() - private fun runTestNotOnMainDispatcher(block: suspend CoroutineScope.() -> Unit) { - // skip if already on the main thread, run blocking doesn't really work well with that - if (!canTestMainDispatcher()) return - runTest(block = block) - } - - @Test - fun testDispatchNecessityCheckWithMainImmediateDispatcher() = runTestNotOnMainDispatcher { - val main = Dispatchers.Main.immediate - assertTrue(main.isDispatchNeeded(EmptyCoroutineContext)) - withContext(Dispatchers.Default) { - assertTrue(main.isDispatchNeeded(EmptyCoroutineContext)) - withContext(Dispatchers.Main) { - assertFalse(main.isDispatchNeeded(EmptyCoroutineContext)) - } - assertTrue(main.isDispatchNeeded(EmptyCoroutineContext)) - } - } - - @Test - fun testWithContext() = runTestNotOnMainDispatcher { - expect(1) - assertFalse(isMainThread()) - withContext(Dispatchers.Main) { - assertTrue(isMainThread()) - expect(2) - } - assertFalse(isMainThread()) - finish(3) - } - - @Test - fun testWithContextDelay() = runTestNotOnMainDispatcher { - expect(1) - withContext(Dispatchers.Main) { - assertTrue(isMainThread()) - expect(2) - delay(100) - assertTrue(isMainThread()) - expect(3) - } - assertFalse(isMainThread()) - finish(4) - } - - @Test - fun testWithTimeoutContextDelayNoTimeout() = runTestNotOnMainDispatcher { - expect(1) - withTimeout(1000) { - withContext(Dispatchers.Main) { - assertTrue(isMainThread()) - expect(2) - delay(100) - assertTrue(isMainThread()) - expect(3) - } - } - assertFalse(isMainThread()) - finish(4) - } - - @Test - fun testWithTimeoutContextDelayTimeout() = runTestNotOnMainDispatcher { - expect(1) - assertFailsWith { - withTimeout(100) { - withContext(Dispatchers.Main) { - assertTrue(isMainThread()) - expect(2) - delay(1000) - expectUnreached() - } - } - expectUnreached() - } - assertFalse(isMainThread()) - finish(3) - } - - @Test - fun testWithContextTimeoutDelayNoTimeout() = runTestNotOnMainDispatcher { - expect(1) - withContext(Dispatchers.Main) { - withTimeout(1000) { - assertTrue(isMainThread()) - expect(2) - delay(100) - assertTrue(isMainThread()) - expect(3) - } - } - assertFalse(isMainThread()) - finish(4) - } + // skip if already on the main thread, run blocking doesn't really work well with that + override fun shouldSkipTesting(): Boolean = isMainThread() - @Test - fun testWithContextTimeoutDelayTimeout() = runTestNotOnMainDispatcher { - expect(1) - assertFailsWith { - withContext(Dispatchers.Main) { - withTimeout(100) { - assertTrue(isMainThread()) - expect(2) - delay(1000) - expectUnreached() - } + override fun scheduleOnMainQueue(block: () -> Unit) { + autoreleasepool { + dispatch_async(dispatch_get_main_queue()) { + block() } - expectUnreached() } - assertFalse(isMainThread()) - finish(3) } } diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt index 7012c23ecd..065bb2af49 100644 --- a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt +++ b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt @@ -127,11 +127,8 @@ internal class HandlerContext private constructor( name: String? = null ) : this(handler, name, false) - @Volatile - private var _immediate: HandlerContext? = if (invokeImmediately) this else null - - override val immediate: HandlerContext = _immediate ?: - HandlerContext(handler, name, true).also { _immediate = it } + override val immediate: HandlerContext = if (invokeImmediately) this else + HandlerContext(handler, name, true) override fun isDispatchNeeded(context: CoroutineContext): Boolean { return !invokeImmediately || Looper.myLooper() != handler.looper @@ -172,8 +169,10 @@ internal class HandlerContext private constructor( if (invokeImmediately) "$str.immediate" else str } - override fun equals(other: Any?): Boolean = other is HandlerContext && other.handler === handler - override fun hashCode(): Int = System.identityHashCode(handler) + override fun equals(other: Any?): Boolean = + other is HandlerContext && other.handler === handler && other.invokeImmediately == invokeImmediately + // inlining `Boolean.hashCode()` for Android compatibility, as requested by Animal Sniffer + override fun hashCode(): Int = System.identityHashCode(handler) xor if (invokeImmediately) 1231 else 1237 } @Volatile diff --git a/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt index afe6cff2f6..9fc40d33ac 100644 --- a/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt +++ b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt @@ -13,31 +13,14 @@ import org.robolectric.annotation.* import org.robolectric.shadows.* import java.util.concurrent.* import kotlin.test.* +import kotlin.time.* +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds @RunWith(RobolectricTestRunner::class) -@Config(manifest = Config.NONE, sdk = [28]) @LooperMode(LooperMode.Mode.LEGACY) -class HandlerDispatcherTest : TestBase() { - @Test - fun testImmediateDispatcherYield() = runBlocking(Dispatchers.Main) { - expect(1) - // launch in the immediate dispatcher - launch(Dispatchers.Main.immediate) { - expect(2) - yield() - expect(4) - } - expect(3) // after yield - yield() // yield back - finish(5) - } - - @Test - fun testMainDispatcherToString() { - assertEquals("Dispatchers.Main", Dispatchers.Main.toString()) - assertEquals("Dispatchers.Main.immediate", Dispatchers.Main.immediate.toString()) - } - +@Config(manifest = Config.NONE, sdk = [28]) +class HandlerDispatcherTest : MainDispatcherTestBase.WithRealTimeDelay() { @Test fun testDefaultDelayIsNotDelegatedToMain() = runTest { val mainLooper = Shadows.shadowOf(Looper.getMainLooper()) @@ -132,4 +115,20 @@ class HandlerDispatcherTest : TestBase() { mainLooper.scheduler.advanceBy(51, TimeUnit.MILLISECONDS) finish(5) } + + override fun isMainThread(): Boolean = Looper.getMainLooper().thread === Thread.currentThread() + + override fun scheduleOnMainQueue(block: () -> Unit) { + Handler(Looper.getMainLooper()).post(block) + } + + // by default, Robolectric only schedules tasks on the main thread but doesn't run them. + // This function nudges it to run them, 10 milliseconds of virtual time at a time. + override suspend fun spinTest(testBody: Job) { + val mainLooper = Shadows.shadowOf(Looper.getMainLooper()) + while (testBody.isActive) { + Thread.sleep(10, 0) + mainLooper.idleFor(10, TimeUnit.MILLISECONDS) + } + } } diff --git a/ui/kotlinx-coroutines-javafx/build.gradle.kts b/ui/kotlinx-coroutines-javafx/build.gradle.kts index 634423a517..d11304e4a7 100644 --- a/ui/kotlinx-coroutines-javafx/build.gradle.kts +++ b/ui/kotlinx-coroutines-javafx/build.gradle.kts @@ -2,15 +2,8 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -buildscript { - dependencies { - // this line can be removed when https://github.com/openjfx/javafx-gradle-plugin/pull/135 is released - classpath("org.javamodularity:moduleplugin:1.8.12") - } -} - plugins { - id("org.openjfx.javafxplugin") version "0.0.13" + id("org.openjfx.javafxplugin") version "0.0.14" } configurations { @@ -33,7 +26,7 @@ javafx { tasks { test { extensions.configure(org.javamodularity.moduleplugin.extensions.TestModuleOptions::class) { - addReads["kotlinx.coroutines.core"] = "junit" + addReads["kotlinx.coroutines.core"] = "junit,kotlin.test" addReads["kotlinx.coroutines.javafx"] = "kotlin.test" } jvmArgs = listOf( diff --git a/ui/kotlinx-coroutines-javafx/test/JavaFxDispatcherTest.kt b/ui/kotlinx-coroutines-javafx/test/JavaFxDispatcherTest.kt index 24c5c132fd..46f6a0249a 100644 --- a/ui/kotlinx-coroutines-javafx/test/JavaFxDispatcherTest.kt +++ b/ui/kotlinx-coroutines-javafx/test/JavaFxDispatcherTest.kt @@ -8,60 +8,33 @@ import javafx.application.* import kotlinx.coroutines.* import org.junit.* import org.junit.Test +import javax.swing.* import kotlin.test.* -class JavaFxDispatcherTest : TestBase() { +class JavaFxDispatcherTest : MainDispatcherTestBase.WithRealTimeDelay() { @Before fun setup() { ignoreLostThreads("JavaFX Application Thread", "Thread-", "QuantumRenderer-", "InvokeLaterDispatcher") } - @Test - fun testDelay() { + override fun shouldSkipTesting(): Boolean { if (!initPlatform()) { println("Skipping JavaFxTest in headless environment") - return // ignore test in headless environments - } - - runBlocking { - expect(1) - val job = launch(Dispatchers.JavaFx) { - check(Platform.isFxApplicationThread()) - expect(2) - delay(100) - check(Platform.isFxApplicationThread()) - expect(3) - } - job.join() - finish(4) + return true // ignore test in headless environments } + return false } - @Test - fun testImmediateDispatcherYield() { - if (!initPlatform()) { - println("Skipping JavaFxTest in headless environment") - return // ignore test in headless environments - } + override fun isMainThread() = Platform.isFxApplicationThread() - runBlocking(Dispatchers.JavaFx) { - expect(1) - check(Platform.isFxApplicationThread()) - // launch in the immediate dispatcher - launch(Dispatchers.JavaFx.immediate) { - expect(2) - yield() - expect(4) - } - expect(3) // after yield - yield() // yield back - finish(5) - } + override fun scheduleOnMainQueue(block: () -> Unit) { + Platform.runLater { block() } } + /** Tests that the Main dispatcher is in fact the JavaFx one. */ @Test - fun testMainDispatcherToString() { - assertEquals("Dispatchers.Main", Dispatchers.Main.toString()) - assertEquals("Dispatchers.Main.immediate", Dispatchers.Main.immediate.toString()) + fun testMainIsJavaFx() { + assertSame(Dispatchers.JavaFx, Dispatchers.Main) } -} \ No newline at end of file + +} diff --git a/ui/kotlinx-coroutines-swing/test/SwingTest.kt b/ui/kotlinx-coroutines-swing/test/SwingTest.kt index 7e53e57b17..6d5111ebec 100644 --- a/ui/kotlinx-coroutines-swing/test/SwingTest.kt +++ b/ui/kotlinx-coroutines-swing/test/SwingTest.kt @@ -11,96 +11,21 @@ import javax.swing.* import kotlin.coroutines.* import kotlin.test.* -class SwingTest : TestBase() { +class SwingTest : MainDispatcherTestBase.WithRealTimeDelay() { @Before fun setup() { ignoreLostThreads("AWT-EventQueue-") } - @Test - fun testDelay() = runBlocking { - expect(1) - SwingUtilities.invokeLater { expect(2) } - val job = launch(Dispatchers.Swing) { - check(SwingUtilities.isEventDispatchThread()) - expect(3) - SwingUtilities.invokeLater { expect(4) } - delay(100) - check(SwingUtilities.isEventDispatchThread()) - expect(5) - } - job.join() - finish(6) - } - - private class SwingComponent(coroutineContext: CoroutineContext = EmptyCoroutineContext) : - CoroutineScope by MainScope() + coroutineContext - { - public var executed = false - fun testLaunch(): Job = launch { - check(SwingUtilities.isEventDispatchThread()) - executed = true - } - fun testFailure(): Job = launch { - check(SwingUtilities.isEventDispatchThread()) - throw TestException() - } - fun testCancellation() : Job = launch(start = CoroutineStart.ATOMIC) { - check(SwingUtilities.isEventDispatchThread()) - delay(Long.MAX_VALUE) - } - } - - @Test - fun testLaunchInMainScope() = runTest { - val component = SwingComponent() - val job = component.testLaunch() - job.join() - assertTrue(component.executed) - component.cancel() - component.coroutineContext[Job]!!.join() - } - - @Test - fun testFailureInMainScope() = runTest { - var exception: Throwable? = null - val component = SwingComponent(CoroutineExceptionHandler { ctx, e -> exception = e}) - val job = component.testFailure() - job.join() - assertTrue(exception!! is TestException) - component.cancel() - join(component) - } - - @Test - fun testCancellationInMainScope() = runTest { - val component = SwingComponent() - component.cancel() - component.testCancellation().join() - join(component) - } - - private suspend fun join(component: SwingComponent) { - component.coroutineContext[Job]!!.join() - } + override fun isMainThread() = SwingUtilities.isEventDispatchThread() - @Test - fun testImmediateDispatcherYield() = runBlocking(Dispatchers.Swing) { - expect(1) - // launch in the immediate dispatcher - launch(Dispatchers.Swing.immediate) { - expect(2) - yield() - expect(4) - } - expect(3) // after yield - yield() // yield back - finish(5) + override fun scheduleOnMainQueue(block: () -> Unit) { + SwingUtilities.invokeLater { block() } } + /** Tests that the Main dispatcher is in fact the JavaFx one. */ @Test - fun testMainDispatcherToString() { - assertEquals("Dispatchers.Main", Dispatchers.Main.toString()) - assertEquals("Dispatchers.Main.immediate", Dispatchers.Main.immediate.toString()) + fun testMainIsJavaFx() { + assertSame(Dispatchers.Swing, Dispatchers.Main) } -} \ No newline at end of file +} From 17bdf4a1cc2fe3facec1b09814c9facc008bf2f0 Mon Sep 17 00:00:00 2001 From: Ilya Goncharov Date: Fri, 1 Dec 2023 13:44:32 +0100 Subject: [PATCH 50/53] Remove metaInfo usage (#3964) * Remove deprecated and to-be-removed property which is actual only for legacy JS compiler --- buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts b/buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts index f8c7ccab8d..5d3902f60a 100644 --- a/buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/kotlin-js-conventions.gradle.kts @@ -36,6 +36,5 @@ tasks.withType { kotlinOptions { moduleKind = "umd" sourceMap = true - metaInfo = true } } From 85afa72f7d57576a40af78524f870eafb2321759 Mon Sep 17 00:00:00 2001 From: igoriakovlev <54274820+igoriakovlev@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:57:00 +0100 Subject: [PATCH 51/53] WASM target implementation (#3849) Fixes #3713 --- README.md | 2 +- build.gradle | 19 +++ buildSrc/src/main/kotlin/KotlinVersion.kt | 14 -- gradle/compile-js-multiplatform.gradle | 7 + ...mpile-jsAndWasmShared-multiplatform.gradle | 21 +++ gradle/compile-native-multiplatform.gradle | 2 - gradle/compile-wasm-multiplatform.gradle | 26 ++++ gradle/dokka.gradle.kts | 6 + integration-testing/build.gradle | 5 + integration-testing/smokeTest/build.gradle | 20 ++- kotlinx-coroutines-core/build.gradle | 11 +- .../js/src/CoroutineContext.kt | 2 +- .../js/src/JSDispatcher.kt | 144 +++--------------- .../internal/CoroutineExceptionHandlerImpl.kt | 20 +-- .../src/CloseableCoroutineDispatcher.kt | 0 .../src/Dispatchers.kt | 2 + .../{js => jsAndWasmShared}/src/EventLoop.kt | 0 .../{js => jsAndWasmShared}/src/Exceptions.kt | 0 .../{js => jsAndWasmShared}/src/Runnable.kt | 0 .../src/SchedulerTask.kt | 0 .../src/flow/internal/FlowExceptions.kt | 0 .../src/flow/internal/SafeCollector.kt | 0 .../src/internal/Concurrent.kt | 0 .../internal/CoroutineExceptionHandlerImpl.kt | 21 +++ .../src/internal/JSDispatcher.kt | 141 +++++++++++++++++ .../src/internal/LinkedList.kt | 0 .../src/internal/LocalAtomics.kt | 0 .../src/internal/ProbesSupport.kt | 0 .../src/internal/StackTraceRecovery.kt | 0 .../src/internal/Synchronized.kt | 2 +- .../src/internal/SystemProps.kt | 0 .../src/internal/ThreadContext.kt | 0 .../src/internal/ThreadLocal.kt | 2 +- .../test/ImmediateDispatcherTest.kt | 2 +- .../test/MessageQueueTest.kt | 0 .../test/SetTimeoutDispatcherTest.kt | 0 .../test/internal/LinkedListTest.kt | 0 .../native/src/internal/Concurrent.kt | 2 + .../native/src/internal/Synchronized.kt | 2 + .../wasmJs/src/CompletionHandler.kt | 22 +++ .../wasmJs/src/CoroutineContext.kt | 51 +++++++ kotlinx-coroutines-core/wasmJs/src/Debug.kt | 20 +++ .../wasmJs/src/JSDispatcher.kt | 92 +++++++++++ kotlinx-coroutines-core/wasmJs/src/Promise.kt | 85 +++++++++++ .../wasmJs/src/internal/CopyOnWriteList.kt | 73 +++++++++ .../internal/CoroutineExceptionHandlerImpl.kt | 12 ++ .../wasmJs/test/PromiseTest.kt | 90 +++++++++++ .../wasmJs/test/TestBase.kt | 143 +++++++++++++++++ kotlinx-coroutines-test/build.gradle.kts | 13 +- .../wasmJs/src/TestBuilders.kt | 19 +++ .../wasmJs/src/internal/TestMainDispatcher.kt | 13 ++ .../wasmJs/test/Helpers.kt | 19 +++ .../wasmJs/test/PromiseTest.kt | 21 +++ 53 files changed, 981 insertions(+), 165 deletions(-) delete mode 100644 buildSrc/src/main/kotlin/KotlinVersion.kt create mode 100644 gradle/compile-jsAndWasmShared-multiplatform.gradle create mode 100644 gradle/compile-wasm-multiplatform.gradle rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/CloseableCoroutineDispatcher.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/Dispatchers.kt (95%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/EventLoop.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/Exceptions.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/Runnable.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/SchedulerTask.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/flow/internal/FlowExceptions.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/flow/internal/SafeCollector.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/internal/Concurrent.kt (100%) create mode 100644 kotlinx-coroutines-core/jsAndWasmShared/src/internal/CoroutineExceptionHandlerImpl.kt create mode 100644 kotlinx-coroutines-core/jsAndWasmShared/src/internal/JSDispatcher.kt rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/internal/LinkedList.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/internal/LocalAtomics.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/internal/ProbesSupport.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/internal/StackTraceRecovery.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/internal/Synchronized.kt (91%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/internal/SystemProps.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/internal/ThreadContext.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/src/internal/ThreadLocal.kt (92%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/test/ImmediateDispatcherTest.kt (94%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/test/MessageQueueTest.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/test/SetTimeoutDispatcherTest.kt (100%) rename kotlinx-coroutines-core/{js => jsAndWasmShared}/test/internal/LinkedListTest.kt (100%) create mode 100644 kotlinx-coroutines-core/wasmJs/src/CompletionHandler.kt create mode 100644 kotlinx-coroutines-core/wasmJs/src/CoroutineContext.kt create mode 100644 kotlinx-coroutines-core/wasmJs/src/Debug.kt create mode 100644 kotlinx-coroutines-core/wasmJs/src/JSDispatcher.kt create mode 100644 kotlinx-coroutines-core/wasmJs/src/Promise.kt create mode 100644 kotlinx-coroutines-core/wasmJs/src/internal/CopyOnWriteList.kt create mode 100644 kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt create mode 100644 kotlinx-coroutines-core/wasmJs/test/PromiseTest.kt create mode 100644 kotlinx-coroutines-core/wasmJs/test/TestBase.kt create mode 100644 kotlinx-coroutines-test/wasmJs/src/TestBuilders.kt create mode 100644 kotlinx-coroutines-test/wasmJs/src/internal/TestMainDispatcher.kt create mode 100644 kotlinx-coroutines-test/wasmJs/test/Helpers.kt create mode 100644 kotlinx-coroutines-test/wasmJs/test/PromiseTest.kt diff --git a/README.md b/README.md index 90ec53e80b..bb771cdd35 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ See [Contributing Guidelines](CONTRIBUTING.md). [Dispatchers.IO]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-i-o.html [asCoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html [Promise.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/await.html -[promise]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/promise.html +[promise]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/[js]promise.html [Window.asCoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html diff --git a/build.gradle b/build.gradle index f6c4cf31c7..4f7a1554c7 100644 --- a/build.gradle +++ b/build.gradle @@ -157,7 +157,12 @@ configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != core apply from: rootProject.file("gradle/compile-native-multiplatform.gradle") } + apply from: rootProject.file("gradle/compile-jsAndWasmShared-multiplatform.gradle") + apply from: rootProject.file("gradle/compile-js-multiplatform.gradle") + + apply from: rootProject.file("gradle/compile-wasm-multiplatform.gradle") + kotlin.sourceSets.commonMain.dependencies { api project(":$coreModule") } @@ -335,3 +340,17 @@ if (CacheRedirector.enabled) { nodeJsExtension.nodeDownloadBaseUrl = CacheRedirector.maybeRedirect(nodeJsExtension.nodeDownloadBaseUrl) } } + +// Drop this configuration when the Node.JS version in KGP will support wasm gc milestone 4 +// check it here: +// https://github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt +extensions.findByType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension.class).with { + // canary nodejs that supports recent Wasm GC changes + it.nodeVersion = "21.0.0-v8-canary202309167e82ab1fa2" + it.nodeDownloadBaseUrl = "https://nodejs.org/download/v8-canary" +} + +// Drop this when node js version become stable +tasks.withType(org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask).configureEach { + args.add("--ignore-engines") +} diff --git a/buildSrc/src/main/kotlin/KotlinVersion.kt b/buildSrc/src/main/kotlin/KotlinVersion.kt deleted file mode 100644 index 5ac051ecad..0000000000 --- a/buildSrc/src/main/kotlin/KotlinVersion.kt +++ /dev/null @@ -1,14 +0,0 @@ -@file:JvmName("KotlinVersion") - -fun isKotlinVersionAtLeast(kotlinVersion: String, atLeastMajor: Int, atLeastMinor: Int, atLeastPatch: Int): Boolean { - val (major, minor) = kotlinVersion - .split('.') - .take(2) - .map { it.toInt() } - val patch = kotlinVersion.substringAfterLast('.').substringBefore('-').toInt() - return when { - major > atLeastMajor -> true - major < atLeastMajor -> false - else -> (minor == atLeastMinor && patch >= atLeastPatch) || minor > atLeastMinor - } -} diff --git a/gradle/compile-js-multiplatform.gradle b/gradle/compile-js-multiplatform.gradle index 4200972cea..1935fbf197 100644 --- a/gradle/compile-js-multiplatform.gradle +++ b/gradle/compile-js-multiplatform.gradle @@ -12,6 +12,13 @@ kotlin { } sourceSets { + jsMain { + dependsOn(jsAndWasmSharedMain) + } + jsTest { + dependsOn(jsAndWasmSharedTest) + } + jsTest.dependencies { api "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version" } diff --git a/gradle/compile-jsAndWasmShared-multiplatform.gradle b/gradle/compile-jsAndWasmShared-multiplatform.gradle new file mode 100644 index 0000000000..8f489019a8 --- /dev/null +++ b/gradle/compile-jsAndWasmShared-multiplatform.gradle @@ -0,0 +1,21 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +kotlin { + sourceSets { + jsAndWasmSharedMain { + dependsOn(commonMain) + } + jsAndWasmSharedTest { + dependsOn(commonTest) + } + } +} + +// Disable intermediate sourceSet compilation because we do not need js-wasmJs artifact +tasks.configureEach { + if (name == 'compileJsAndWasmSharedMainKotlinMetadata') { + enabled = false + } +} diff --git a/gradle/compile-native-multiplatform.gradle b/gradle/compile-native-multiplatform.gradle index 91a6b62c6b..4f74cf5278 100644 --- a/gradle/compile-native-multiplatform.gradle +++ b/gradle/compile-native-multiplatform.gradle @@ -1,5 +1,3 @@ -import static KotlinVersion.isKotlinVersionAtLeast - /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ diff --git a/gradle/compile-wasm-multiplatform.gradle b/gradle/compile-wasm-multiplatform.gradle new file mode 100644 index 0000000000..3692c6d6b8 --- /dev/null +++ b/gradle/compile-wasm-multiplatform.gradle @@ -0,0 +1,26 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +kotlin { + wasmJs { + moduleName = project.name + nodejs() + compilations['main']?.dependencies { + api "org.jetbrains.kotlinx:atomicfu-wasm-js:$atomicfu_version" + } + } + + sourceSets { + wasmJsMain { + dependsOn(jsAndWasmSharedMain) + } + wasmJsTest { + dependsOn(jsAndWasmSharedTest) + } + + wasmJsTest.dependencies { + api "org.jetbrains.kotlin:kotlin-test-wasm-js:$kotlin_version" + } + } +} \ No newline at end of file diff --git a/gradle/dokka.gradle.kts b/gradle/dokka.gradle.kts index ba6956aa83..829faad0a0 100644 --- a/gradle/dokka.gradle.kts +++ b/gradle/dokka.gradle.kts @@ -45,6 +45,8 @@ tasks.withType(DokkaTaskPartial::class).configureEach { } } +val kotlin_version: String by project + if (project.name == "kotlinx-coroutines-core") { // Custom configuration for MPP modules tasks.withType(DokkaTaskPartial::class).configureEach { @@ -64,6 +66,10 @@ if (project.name == "kotlinx-coroutines-core") { val jvmMain by getting { makeLinkMapping(project.file("jvm")) } + + val wasmJsMain by getting { + makeLinkMapping(project.file("wasm")) + } } } } diff --git a/integration-testing/build.gradle b/integration-testing/build.gradle index 26ee9d99dc..1a231afbdf 100644 --- a/integration-testing/build.gradle +++ b/integration-testing/build.gradle @@ -179,3 +179,8 @@ compileKotlin { jvmTarget = "1.8" } } + +// Drop this when node js version become stable +tasks.withType(org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask.class).configureEach { + it.args.add("--ignore-engines") +} diff --git a/integration-testing/smokeTest/build.gradle b/integration-testing/smokeTest/build.gradle index 26cd02b600..ad6a485ed2 100644 --- a/integration-testing/smokeTest/build.gradle +++ b/integration-testing/smokeTest/build.gradle @@ -3,10 +3,10 @@ plugins { } repositories { - // Coroutines from the outer project are published by previous CI buils step - mavenLocal() mavenCentral() maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" } + // Coroutines from the outer project are published by previous CI buils step + mavenLocal() } kotlin { @@ -14,6 +14,9 @@ kotlin { js(IR) { nodejs() } + wasmJs() { + nodejs() + } sourceSets { commonMain { @@ -34,6 +37,11 @@ kotlin { implementation kotlin('test-js') } } + wasmJsTest { + dependencies { + implementation kotlin('test-wasm-js') + } + } jvmTest { dependencies { implementation kotlin('test') @@ -50,3 +58,11 @@ kotlin { } } +// Drop this configuration when the Node.JS version in KGP will support wasm gc milestone 4 +// check it here: +// https://github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt +rootProject.extensions.findByType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension.class).with { + // canary nodejs that supports recent Wasm GC changes + it.nodeVersion = "21.0.0-v8-canary202309167e82ab1fa2" + it.nodeDownloadBaseUrl = "https://nodejs.org/download/v8-canary" +} \ No newline at end of file diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index c8d1c3c197..fbd62e0cf8 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -19,8 +19,12 @@ if (rootProject.ext.native_targets_enabled) { apply from: rootProject.file("gradle/compile-native-multiplatform.gradle") } +apply from: rootProject.file("gradle/compile-jsAndWasmShared-multiplatform.gradle") + apply from: rootProject.file("gradle/compile-js-multiplatform.gradle") +apply from: rootProject.file("gradle/compile-wasm-multiplatform.gradle") + apply from: rootProject.file('gradle/dokka.gradle.kts') apply from: rootProject.file('gradle/publish.gradle') /* ========================================================================== @@ -28,9 +32,8 @@ apply from: rootProject.file('gradle/publish.gradle') TARGETS SOURCE SETS ------- ---------------------------------------------- - - js -----------------------------------------------------+ - | + wasmJs \----------> jsAndWasmShared --------------------+ + js / | V jvmCore\ --------> jvm ---------> concurrent -------> common jdk8 / ^ @@ -242,7 +245,7 @@ kotlin.sourceSets { kotlin.sourceSets.configureEach { // Do not apply 'ExperimentalForeignApi' where we have allWarningsAsErrors set - if (it.name in ["jvmMain", "jvmCoreMain", "jsMain", "concurrentMain", "commonMain"]) return + if (it.name in ["jvmMain", "jvmCoreMain", "jsMain", 'wasmJsMain', 'jsAndWasmSharedMain', "concurrentMain", "commonMain"]) return languageSettings { optIn('kotlinx.cinterop.ExperimentalForeignApi') optIn('kotlin.experimental.ExperimentalNativeApi') diff --git a/kotlinx-coroutines-core/js/src/CoroutineContext.kt b/kotlinx-coroutines-core/js/src/CoroutineContext.kt index 232b3e271b..2d80a90730 100644 --- a/kotlinx-coroutines-core/js/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/js/src/CoroutineContext.kt @@ -12,7 +12,7 @@ private external val navigator: dynamic private const val UNDEFINED = "undefined" internal external val process: dynamic -internal fun createDefaultDispatcher(): CoroutineDispatcher = when { +internal actual fun createDefaultDispatcher(): CoroutineDispatcher = when { // Check if we are running under jsdom. WindowDispatcher doesn't work under jsdom because it accesses MessageEvent#source. // It is not implemented in jsdom, see https://github.com/jsdom/jsdom/blob/master/Changelog.md // "It's missing a few semantics, especially around origins, as well as MessageEvent source." diff --git a/kotlinx-coroutines-core/js/src/JSDispatcher.kt b/kotlinx-coroutines-core/js/src/JSDispatcher.kt index 8ddb903339..c94985b1c8 100644 --- a/kotlinx-coroutines-core/js/src/JSDispatcher.kt +++ b/kotlinx-coroutines-core/js/src/JSDispatcher.kt @@ -4,50 +4,36 @@ package kotlinx.coroutines -import kotlinx.coroutines.internal.* import org.w3c.dom.* -import kotlin.coroutines.* import kotlin.js.Promise -private const val MAX_DELAY = Int.MAX_VALUE.toLong() +public actual typealias W3CWindow = Window -private fun delayToInt(timeMillis: Long): Int = - timeMillis.coerceIn(0, MAX_DELAY).toInt() +internal actual fun w3cSetTimeout(window: W3CWindow, handler: () -> Unit, timeout: Int): Int = + setTimeout(window, handler, timeout) -internal sealed class SetTimeoutBasedDispatcher: CoroutineDispatcher(), Delay { - inner class ScheduledMessageQueue : MessageQueue() { - internal val processQueue: dynamic = { process() } +internal actual fun w3cSetTimeout(handler: () -> Unit, timeout: Int): Int = + setTimeout(handler, timeout) - override fun schedule() { - scheduleQueueProcessing() - } +internal actual fun w3cClearTimeout(window: W3CWindow, handle: Int) = + window.clearTimeout(handle) - override fun reschedule() { - setTimeout(processQueue, 0) - } - } - - internal val messageQueue = ScheduledMessageQueue() +internal actual fun w3cClearTimeout(handle: Int) = + clearTimeout(handle) - abstract fun scheduleQueueProcessing() +internal actual class ScheduledMessageQueue actual constructor(private val dispatcher: SetTimeoutBasedDispatcher) : MessageQueue() { + internal val processQueue: dynamic = { process() } - override fun limitedParallelism(parallelism: Int): CoroutineDispatcher { - parallelism.checkParallelism() - return this + actual override fun schedule() { + dispatcher.scheduleQueueProcessing() } - override fun dispatch(context: CoroutineContext, block: Runnable) { - messageQueue.enqueue(block) + actual override fun reschedule() { + setTimeout(processQueue, 0) } - override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { - val handle = setTimeout({ block.run() }, delayToInt(timeMillis)) - return ClearTimeout(handle) - } - - override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { - val handle = setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis)) - continuation.invokeOnCancellation(handler = ClearTimeout(handle).asHandler) + internal actual fun setTimeout(timeout: Int) { + setTimeout(processQueue, timeout) } } @@ -57,48 +43,7 @@ internal object NodeDispatcher : SetTimeoutBasedDispatcher() { } } -internal object SetTimeoutDispatcher : SetTimeoutBasedDispatcher() { - override fun scheduleQueueProcessing() { - setTimeout(messageQueue.processQueue, 0) - } -} - -private open class ClearTimeout(protected val handle: Int) : CancelHandler(), DisposableHandle { - - override fun dispose() { - clearTimeout(handle) - } - - override fun invoke(cause: Throwable?) { - dispose() - } - - override fun toString(): String = "ClearTimeout[$handle]" -} - -internal class WindowDispatcher(private val window: Window) : CoroutineDispatcher(), Delay { - private val queue = WindowMessageQueue(window) - - override fun dispatch(context: CoroutineContext, block: Runnable) = queue.enqueue(block) - - override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { - val handle = window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis)) - continuation.invokeOnCancellation(handler = WindowClearTimeout(handle).asHandler) - } - - override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { - val handle = window.setTimeout({ block.run() }, delayToInt(timeMillis)) - return WindowClearTimeout(handle) - } - - private inner class WindowClearTimeout(handle: Int) : ClearTimeout(handle) { - override fun dispose() { - window.clearTimeout(handle) - } - } -} - -private class WindowMessageQueue(private val window: Window) : MessageQueue() { +internal actual class WindowMessageQueue actual constructor(private val window: W3CWindow) : MessageQueue() { private val messageName = "dispatchCoroutine" init { @@ -110,61 +55,20 @@ private class WindowMessageQueue(private val window: Window) : MessageQueue() { }, true) } - override fun schedule() { + actual override fun schedule() { Promise.resolve(Unit).then({ process() }) } - override fun reschedule() { + actual override fun reschedule() { window.postMessage(messageName, "*") } } -/** - * An abstraction over JS scheduling mechanism that leverages micro-batching of dispatched blocks without - * paying the cost of JS callbacks scheduling on every dispatch. - * - * Queue uses two scheduling mechanisms: - * 1) [schedule] is used to schedule the initial processing of the message queue. - * JS engine-specific microtask mechanism is used in order to boost performance on short runs and a dispatch batch - * 2) [reschedule] is used to schedule processing of the queue after yield to the JS event loop. - * JS engine-specific macrotask mechanism is used not to starve animations and non-coroutines macrotasks. - * - * Yet there could be a long tail of "slow" reschedules, but it should be amortized by the queue size. - */ -internal abstract class MessageQueue : MutableList by ArrayDeque() { - val yieldEvery = 16 // yield to JS macrotask event loop after this many processed messages - private var scheduled = false - - abstract fun schedule() - - abstract fun reschedule() - - fun enqueue(element: Runnable) { - add(element) - if (!scheduled) { - scheduled = true - schedule() - } - } - - fun process() { - try { - // limit number of processed messages - repeat(yieldEvery) { - val element = removeFirstOrNull() ?: return@process - element.run() - } - } finally { - if (isEmpty()) { - scheduled = false - } else { - reschedule() - } - } - } -} - // We need to reference global setTimeout and clearTimeout so that it works on Node.JS as opposed to // using them via "window" (which only works in browser) private external fun setTimeout(handler: dynamic, timeout: Int = definedExternally): Int + private external fun clearTimeout(handle: Int = definedExternally) + +private fun setTimeout(window: Window, handler: () -> Unit, timeout: Int): Int = + window.setTimeout(handler, timeout) diff --git a/kotlinx-coroutines-core/js/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/js/src/internal/CoroutineExceptionHandlerImpl.kt index 675cc4a67a..097f4bb607 100644 --- a/kotlinx-coroutines-core/js/src/internal/CoroutineExceptionHandlerImpl.kt +++ b/kotlinx-coroutines-core/js/src/internal/CoroutineExceptionHandlerImpl.kt @@ -1,26 +1,12 @@ /* - * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 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 kotlin.coroutines.* - -private val platformExceptionHandlers_ = mutableSetOf() - -internal actual val platformExceptionHandlers: Collection - get() = platformExceptionHandlers_ - -internal actual fun ensurePlatformExceptionHandlerLoaded(callback: CoroutineExceptionHandler) { - platformExceptionHandlers_ += callback -} internal actual fun propagateExceptionFinalResort(exception: Throwable) { // log exception - console.error(exception) -} - -internal actual class DiagnosticCoroutineContextException actual constructor(context: CoroutineContext) : - RuntimeException(context.toString()) - + console.error(exception.toString()) +} \ No newline at end of file diff --git a/kotlinx-coroutines-core/js/src/CloseableCoroutineDispatcher.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/CloseableCoroutineDispatcher.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/CloseableCoroutineDispatcher.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/CloseableCoroutineDispatcher.kt diff --git a/kotlinx-coroutines-core/js/src/Dispatchers.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/Dispatchers.kt similarity index 95% rename from kotlinx-coroutines-core/js/src/Dispatchers.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/Dispatchers.kt index 1304c5a9e5..622344b577 100644 --- a/kotlinx-coroutines-core/js/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/jsAndWasmShared/src/Dispatchers.kt @@ -6,6 +6,8 @@ package kotlinx.coroutines import kotlin.coroutines.* +internal expect fun createDefaultDispatcher(): CoroutineDispatcher + public actual object Dispatchers { public actual val Default: CoroutineDispatcher = createDefaultDispatcher() public actual val Main: MainCoroutineDispatcher diff --git a/kotlinx-coroutines-core/js/src/EventLoop.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/EventLoop.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/EventLoop.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/EventLoop.kt diff --git a/kotlinx-coroutines-core/js/src/Exceptions.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/Exceptions.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/Exceptions.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/Exceptions.kt diff --git a/kotlinx-coroutines-core/js/src/Runnable.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/Runnable.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/Runnable.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/Runnable.kt diff --git a/kotlinx-coroutines-core/js/src/SchedulerTask.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/SchedulerTask.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/SchedulerTask.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/SchedulerTask.kt diff --git a/kotlinx-coroutines-core/js/src/flow/internal/FlowExceptions.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/flow/internal/FlowExceptions.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/flow/internal/FlowExceptions.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/flow/internal/FlowExceptions.kt diff --git a/kotlinx-coroutines-core/js/src/flow/internal/SafeCollector.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/flow/internal/SafeCollector.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/flow/internal/SafeCollector.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/flow/internal/SafeCollector.kt diff --git a/kotlinx-coroutines-core/js/src/internal/Concurrent.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/Concurrent.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/internal/Concurrent.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/internal/Concurrent.kt diff --git a/kotlinx-coroutines-core/jsAndWasmShared/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/CoroutineExceptionHandlerImpl.kt new file mode 100644 index 0000000000..0612922edd --- /dev/null +++ b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/CoroutineExceptionHandlerImpl.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2016-2022 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 kotlin.coroutines.* + +private val platformExceptionHandlers_ = mutableSetOf() + +internal actual val platformExceptionHandlers: Collection + get() = platformExceptionHandlers_ + +internal actual fun ensurePlatformExceptionHandlerLoaded(callback: CoroutineExceptionHandler) { + platformExceptionHandlers_ += callback +} + +internal actual class DiagnosticCoroutineContextException actual constructor(context: CoroutineContext) : + RuntimeException(context.toString()) + diff --git a/kotlinx-coroutines-core/jsAndWasmShared/src/internal/JSDispatcher.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/JSDispatcher.kt new file mode 100644 index 0000000000..b93c0b35f0 --- /dev/null +++ b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/JSDispatcher.kt @@ -0,0 +1,141 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlinx.coroutines.internal.* +import kotlin.coroutines.* + +public expect abstract class W3CWindow +internal expect fun w3cSetTimeout(window: W3CWindow, handler: () -> Unit, timeout: Int): Int +internal expect fun w3cSetTimeout(handler: () -> Unit, timeout: Int): Int +internal expect fun w3cClearTimeout(handle: Int) +internal expect fun w3cClearTimeout(window: W3CWindow, handle: Int) + +internal expect class ScheduledMessageQueue(dispatcher: SetTimeoutBasedDispatcher) : MessageQueue { + override fun schedule() + override fun reschedule() + internal fun setTimeout(timeout: Int) +} + +internal expect class WindowMessageQueue(window: W3CWindow) : MessageQueue { + override fun schedule() + override fun reschedule() +} + +private const val MAX_DELAY = Int.MAX_VALUE.toLong() + +private fun delayToInt(timeMillis: Long): Int = + timeMillis.coerceIn(0, MAX_DELAY).toInt() + +internal abstract class SetTimeoutBasedDispatcher: CoroutineDispatcher(), Delay { + internal val messageQueue = ScheduledMessageQueue(this) + + abstract fun scheduleQueueProcessing() + + override fun limitedParallelism(parallelism: Int): CoroutineDispatcher { + parallelism.checkParallelism() + return this + } + + override fun dispatch(context: CoroutineContext, block: Runnable) { + messageQueue.enqueue(block) + } + + override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { + val handle = w3cSetTimeout({ block.run() }, delayToInt(timeMillis)) + return ClearTimeout(handle) + } + + override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { + val handle = w3cSetTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis)) + continuation.invokeOnCancellation(handler = ClearTimeout(handle).asHandler) + } +} + +internal class WindowDispatcher(private val window: W3CWindow) : CoroutineDispatcher(), Delay { + private val queue = WindowMessageQueue(window) + + override fun dispatch(context: CoroutineContext, block: Runnable) = queue.enqueue(block) + + override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { + val handle = w3cSetTimeout(window, { with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis)) + continuation.invokeOnCancellation(handler = WindowClearTimeout(handle).asHandler) + } + + override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { + val handle = w3cSetTimeout(window, block::run, delayToInt(timeMillis)) + return WindowClearTimeout(handle) + } + + private inner class WindowClearTimeout(handle: Int) : ClearTimeout(handle) { + override fun dispose() { + w3cClearTimeout(window, handle) + } + } +} + +internal object SetTimeoutDispatcher : SetTimeoutBasedDispatcher() { + override fun scheduleQueueProcessing() { + messageQueue.setTimeout(0) + } +} + +private open class ClearTimeout(protected val handle: Int) : CancelHandler(), DisposableHandle { + override fun dispose() { + w3cClearTimeout(handle) + } + + override fun invoke(cause: Throwable?) { + dispose() + } + + override fun toString(): String = "ClearTimeout[$handle]" +} + + +/** + * An abstraction over JS scheduling mechanism that leverages micro-batching of dispatched blocks without + * paying the cost of JS callbacks scheduling on every dispatch. + * + * Queue uses two scheduling mechanisms: + * 1) [schedule] is used to schedule the initial processing of the message queue. + * JS engine-specific microtask mechanism is used in order to boost performance on short runs and a dispatch batch + * 2) [reschedule] is used to schedule processing of the queue after yield to the JS event loop. + * JS engine-specific macrotask mechanism is used not to starve animations and non-coroutines macrotasks. + * + * Yet there could be a long tail of "slow" reschedules, but it should be amortized by the queue size. + */ +internal abstract class MessageQueue : MutableList by ArrayDeque() { + val yieldEvery = 16 // yield to JS macrotask event loop after this many processed messages + private var scheduled = false + + abstract fun schedule() + + abstract fun reschedule() + + fun enqueue(element: Runnable) { + add(element) + if (!scheduled) { + scheduled = true + schedule() + } + } + + fun process() { + try { + // limit number of processed messages + repeat(yieldEvery) { + val element = removeFirstOrNull() ?: return@process + element.run() + } + } finally { + if (isEmpty()) { + scheduled = false + } else { + reschedule() + } + } + } +} diff --git a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/LinkedList.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/internal/LinkedList.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/internal/LinkedList.kt diff --git a/kotlinx-coroutines-core/js/src/internal/LocalAtomics.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/LocalAtomics.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/internal/LocalAtomics.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/internal/LocalAtomics.kt diff --git a/kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/ProbesSupport.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/internal/ProbesSupport.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/internal/ProbesSupport.kt diff --git a/kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/StackTraceRecovery.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/internal/StackTraceRecovery.kt diff --git a/kotlinx-coroutines-core/js/src/internal/Synchronized.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/Synchronized.kt similarity index 91% rename from kotlinx-coroutines-core/js/src/internal/Synchronized.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/internal/Synchronized.kt index 05db52854f..91c422f237 100644 --- a/kotlinx-coroutines-core/js/src/internal/Synchronized.kt +++ b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/Synchronized.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.* * @suppress **This an internal API and should not be used from general code.** */ @InternalCoroutinesApi -public actual typealias SynchronizedObject = Any +public actual open class SynchronizedObject /** * @suppress **This an internal API and should not be used from general code.** diff --git a/kotlinx-coroutines-core/js/src/internal/SystemProps.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/SystemProps.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/internal/SystemProps.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/internal/SystemProps.kt diff --git a/kotlinx-coroutines-core/js/src/internal/ThreadContext.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/ThreadContext.kt similarity index 100% rename from kotlinx-coroutines-core/js/src/internal/ThreadContext.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/internal/ThreadContext.kt diff --git a/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/ThreadLocal.kt similarity index 92% rename from kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt rename to kotlinx-coroutines-core/jsAndWasmShared/src/internal/ThreadLocal.kt index c8dd09683f..8800e281e3 100644 --- a/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt +++ b/kotlinx-coroutines-core/jsAndWasmShared/src/internal/ThreadLocal.kt @@ -11,4 +11,4 @@ internal actual class CommonThreadLocal { actual fun set(value: T) { this.value = value } } -internal actual fun commonThreadLocal(name: Symbol): CommonThreadLocal = CommonThreadLocal() +internal actual fun commonThreadLocal(name: Symbol): CommonThreadLocal = CommonThreadLocal() \ No newline at end of file diff --git a/kotlinx-coroutines-core/js/test/ImmediateDispatcherTest.kt b/kotlinx-coroutines-core/jsAndWasmShared/test/ImmediateDispatcherTest.kt similarity index 94% rename from kotlinx-coroutines-core/js/test/ImmediateDispatcherTest.kt rename to kotlinx-coroutines-core/jsAndWasmShared/test/ImmediateDispatcherTest.kt index 09b96eb6ec..ac249560b9 100644 --- a/kotlinx-coroutines-core/js/test/ImmediateDispatcherTest.kt +++ b/kotlinx-coroutines-core/jsAndWasmShared/test/ImmediateDispatcherTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.coroutines diff --git a/kotlinx-coroutines-core/js/test/MessageQueueTest.kt b/kotlinx-coroutines-core/jsAndWasmShared/test/MessageQueueTest.kt similarity index 100% rename from kotlinx-coroutines-core/js/test/MessageQueueTest.kt rename to kotlinx-coroutines-core/jsAndWasmShared/test/MessageQueueTest.kt diff --git a/kotlinx-coroutines-core/js/test/SetTimeoutDispatcherTest.kt b/kotlinx-coroutines-core/jsAndWasmShared/test/SetTimeoutDispatcherTest.kt similarity index 100% rename from kotlinx-coroutines-core/js/test/SetTimeoutDispatcherTest.kt rename to kotlinx-coroutines-core/jsAndWasmShared/test/SetTimeoutDispatcherTest.kt diff --git a/kotlinx-coroutines-core/js/test/internal/LinkedListTest.kt b/kotlinx-coroutines-core/jsAndWasmShared/test/internal/LinkedListTest.kt similarity index 100% rename from kotlinx-coroutines-core/js/test/internal/LinkedListTest.kt rename to kotlinx-coroutines-core/jsAndWasmShared/test/internal/LinkedListTest.kt diff --git a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt index f46326bcda..0cc78378da 100644 --- a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt +++ b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt @@ -5,11 +5,13 @@ package kotlinx.coroutines.internal import kotlinx.atomicfu.* +import kotlinx.cinterop.* import kotlinx.atomicfu.locks.withLock as withLock2 @Suppress("ACTUAL_WITHOUT_EXPECT") internal actual typealias ReentrantLock = kotlinx.atomicfu.locks.SynchronizedObject +@OptIn(UnsafeNumber::class) internal actual inline fun ReentrantLock.withLock(action: () -> T): T = this.withLock2(action) internal actual fun identitySet(expectedSize: Int): MutableSet = HashSet() diff --git a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt index 8a8ecfe393..20fc666229 100644 --- a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt +++ b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt @@ -4,6 +4,7 @@ package kotlinx.coroutines.internal +import kotlinx.cinterop.* import kotlinx.coroutines.* import kotlinx.atomicfu.locks.withLock as withLock2 @@ -16,5 +17,6 @@ public actual typealias SynchronizedObject = kotlinx.atomicfu.locks.Synchronized /** * @suppress **This an internal API and should not be used from general code.** */ +@OptIn(UnsafeNumber::class) @InternalCoroutinesApi public actual inline fun synchronizedImpl(lock: SynchronizedObject, block: () -> T): T = lock.withLock2(block) diff --git a/kotlinx-coroutines-core/wasmJs/src/CompletionHandler.kt b/kotlinx-coroutines-core/wasmJs/src/CompletionHandler.kt new file mode 100644 index 0000000000..4835f7968e --- /dev/null +++ b/kotlinx-coroutines-core/wasmJs/src/CompletionHandler.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlinx.coroutines.internal.* + +internal actual abstract class CompletionHandlerBase actual constructor() : LockFreeLinkedListNode(), CompletionHandler { + actual abstract override fun invoke(cause: Throwable?) +} + +internal actual inline val CompletionHandlerBase.asHandler: CompletionHandler get() = this + +internal actual abstract class CancelHandlerBase actual constructor() : CompletionHandler { + actual abstract override fun invoke(cause: Throwable?) +} + +internal actual inline val CancelHandlerBase.asHandler: CompletionHandler get() = this + +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun CompletionHandler.invokeIt(cause: Throwable?) = invoke(cause) diff --git a/kotlinx-coroutines-core/wasmJs/src/CoroutineContext.kt b/kotlinx-coroutines-core/wasmJs/src/CoroutineContext.kt new file mode 100644 index 0000000000..ab37ff88d3 --- /dev/null +++ b/kotlinx-coroutines-core/wasmJs/src/CoroutineContext.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlinx.coroutines.internal.* +import org.w3c.dom.* +import kotlin.coroutines.* + +internal external interface JsProcess : JsAny { + fun nextTick(handler: () -> Unit) +} + +internal fun tryGetProcess(): JsProcess? = + js("(typeof(process) !== 'undefined' && typeof(process.nextTick) === 'function') ? process : null") + +internal fun tryGetWindow(): Window? = + js("(typeof(window) !== 'undefined' && window != null && typeof(window.addEventListener) === 'function') ? window : null") + +internal actual fun createDefaultDispatcher(): CoroutineDispatcher = + tryGetProcess()?.let(::NodeDispatcher) + ?: tryGetWindow()?.let(::WindowDispatcher) + ?: SetTimeoutDispatcher + +@PublishedApi // Used from kotlinx-coroutines-test via suppress, not part of ABI +internal actual val DefaultDelay: Delay + get() = Dispatchers.Default as Delay + +public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext { + val combined = coroutineContext + context + return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null) + combined + Dispatchers.Default else combined +} + +public actual fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext { + return this + addedContext +} + +// No debugging facilities on Wasm +internal actual inline fun withCoroutineContext(context: CoroutineContext, countOrElement: Any?, block: () -> T): T = block() +internal actual inline fun withContinuationContext(continuation: Continuation<*>, countOrElement: Any?, block: () -> T): T = block() +internal actual fun Continuation<*>.toDebugString(): String = toString() +internal actual val CoroutineContext.coroutineName: String? get() = null // not supported on Wasm + +internal actual class UndispatchedCoroutine actual constructor( + context: CoroutineContext, + uCont: Continuation +) : ScopeCoroutine(context, uCont) { + override fun afterResume(state: Any?) = uCont.resumeWith(recoverResult(state, uCont)) +} diff --git a/kotlinx-coroutines-core/wasmJs/src/Debug.kt b/kotlinx-coroutines-core/wasmJs/src/Debug.kt new file mode 100644 index 0000000000..93e253039f --- /dev/null +++ b/kotlinx-coroutines-core/wasmJs/src/Debug.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +internal actual val DEBUG: Boolean = false + +internal actual val Any.hexAddress: String + get() = this.hashCode().toString() + +internal actual val Any.classSimpleName: String get() = this::class.simpleName ?: "Unknown" + +internal actual inline fun assert(value: () -> Boolean) {} + +internal external interface Console { + fun error(s: String) +} + +internal external val console: Console \ No newline at end of file diff --git a/kotlinx-coroutines-core/wasmJs/src/JSDispatcher.kt b/kotlinx-coroutines-core/wasmJs/src/JSDispatcher.kt new file mode 100644 index 0000000000..3dfdeff321 --- /dev/null +++ b/kotlinx-coroutines-core/wasmJs/src/JSDispatcher.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import org.w3c.dom.Window +import kotlin.js.* + +public actual typealias W3CWindow = Window + +internal actual fun w3cSetTimeout(window: W3CWindow, handler: () -> Unit, timeout: Int): Int = + setTimeout(window, handler, timeout) + +internal actual fun w3cSetTimeout(handler: () -> Unit, timeout: Int): Int = + setTimeout(handler, timeout) + +internal actual fun w3cClearTimeout(window: W3CWindow, handle: Int) = + window.clearTimeout(handle) + +internal actual fun w3cClearTimeout(handle: Int) = + clearTimeout(handle) + +internal actual class ScheduledMessageQueue actual constructor(private val dispatcher: SetTimeoutBasedDispatcher) : MessageQueue() { + internal val processQueue: () -> Unit = ::process + + actual override fun schedule() { + dispatcher.scheduleQueueProcessing() + } + + actual override fun reschedule() { + setTimeout(processQueue, 0) + } + + internal actual fun setTimeout(timeout: Int) { + setTimeout(processQueue, timeout) + } +} + +internal class NodeDispatcher(private val process: JsProcess) : SetTimeoutBasedDispatcher() { + override fun scheduleQueueProcessing() { + process.nextTick(messageQueue.processQueue) + } +} + +@Suppress("UNUSED_PARAMETER") +private fun subscribeToWindowMessages(window: Window, process: () -> Unit): Unit = js("""{ + const handler = (event) => { + if (event.source == window && event.data == 'dispatchCoroutine') { + event.stopPropagation(); + process(); + } + } + window.addEventListener('message', handler, true); +}""") + +@Suppress("UNUSED_PARAMETER") +private fun createRescheduleMessagePoster(window: Window): () -> Unit = + js("() => window.postMessage('dispatchCoroutine', '*')") + +@Suppress("UNUSED_PARAMETER") +private fun createScheduleMessagePoster(process: () -> Unit): () -> Unit = + js("() => Promise.resolve(0).then(process)") + +internal actual class WindowMessageQueue actual constructor(window: W3CWindow) : MessageQueue() { + private val scheduleMessagePoster = createScheduleMessagePoster(::process) + private val rescheduleMessagePoster = createRescheduleMessagePoster(window) + init { + subscribeToWindowMessages(window, ::process) + } + + actual override fun schedule() { + scheduleMessagePoster() + } + + actual override fun reschedule() { + rescheduleMessagePoster() + } +} + +// We need to reference global setTimeout and clearTimeout so that it works on Node.JS as opposed to +// using them via "window" (which only works in browser) +private external fun setTimeout(handler: () -> Unit, timeout: Int): Int + +// d8 doesn't have clearTimeout +@Suppress("UNUSED_PARAMETER") +private fun clearTimeout(handle: Int): Unit = + js("{ if (typeof clearTimeout !== 'undefined') clearTimeout(handle); }") + +@Suppress("UNUSED_PARAMETER") +private fun setTimeout(window: Window, handler: () -> Unit, timeout: Int): Int = + js("window.setTimeout(handler, timeout)") diff --git a/kotlinx-coroutines-core/wasmJs/src/Promise.kt b/kotlinx-coroutines-core/wasmJs/src/Promise.kt new file mode 100644 index 0000000000..f20bee3320 --- /dev/null +++ b/kotlinx-coroutines-core/wasmJs/src/Promise.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlin.coroutines.* +import kotlin.js.* + +@Suppress("UNUSED_PARAMETER") +internal fun promiseSetDeferred(promise: Promise, deferred: JsAny): Unit = + js("promise.deferred = deferred") + +@Suppress("UNUSED_PARAMETER") +internal fun promiseGetDeferred(promise: Promise): JsAny? = js("""{ + console.assert(promise instanceof Promise, "promiseGetDeferred must receive a promise, but got ", promise); + return promise.deferred == null ? null : promise.deferred; +}""") + + +/** + * Starts new coroutine and returns its result as an implementation of [Promise]. + * + * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument. + * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used. + * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden + * with corresponding [context] element. + * + * By default, the coroutine is immediately scheduled for execution. + * Other options can be specified via `start` parameter. See [CoroutineStart] for details. + * + * @param context additional to [CoroutineScope.coroutineContext] context of the coroutine. + * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. + * @param block the coroutine code. + */ +public fun CoroutineScope.promise( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> T +): Promise = + async(context, start, block).asPromise() + +/** + * Converts this deferred value to the instance of [Promise]. + */ +public fun Deferred.asPromise(): Promise { + val promise = Promise { resolve, reject -> + invokeOnCompletion { + val e = getCompletionExceptionOrNull() + if (e != null) { + reject(e.toJsReference()) + } else { + resolve(getCompleted()?.toJsReference()) + } + } + } + promiseSetDeferred(promise, this.toJsReference()) + return promise +} + +/** + * Converts this promise value to the instance of [Deferred]. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UNCHECKED_CAST") +public fun Promise.asDeferred(): Deferred { + val deferred = promiseGetDeferred(this) as? JsReference> + return deferred?.get() ?: GlobalScope.async(start = CoroutineStart.UNDISPATCHED) { await() } +} + +/** + * Awaits for completion of the promise without blocking. + * + * This suspending function is cancellable. + * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function + * stops waiting for the promise and immediately resumes with [CancellationException]. + * There is a **prompt cancellation guarantee**. If the job was cancelled while this function was + * suspended, it will not resume successfully. See [suspendCancellableCoroutine] documentation for low-level details. + */ +@Suppress("UNCHECKED_CAST") +public suspend fun Promise.await(): T = suspendCancellableCoroutine { cont: CancellableContinuation -> + this@await.then( + onFulfilled = { cont.resume(it as T); null }, + onRejected = { cont.resumeWithException(it.toThrowableOrNull() ?: error("Unexpected non-Kotlin exception $it")); null } + ) +} diff --git a/kotlinx-coroutines-core/wasmJs/src/internal/CopyOnWriteList.kt b/kotlinx-coroutines-core/wasmJs/src/internal/CopyOnWriteList.kt new file mode 100644 index 0000000000..5d6f79fbcd --- /dev/null +++ b/kotlinx-coroutines-core/wasmJs/src/internal/CopyOnWriteList.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.internal + +@Suppress("UNCHECKED_CAST") +internal class CopyOnWriteList : AbstractMutableList() { + private var array: Array = arrayOfNulls(0) + + override val size: Int + get() = array.size + + override fun add(element: E): Boolean { + val n = size + val update = array.copyOf(n + 1) + update[n] = element + array = update + return true + } + + override fun add(index: Int, element: E) { + rangeCheck(index) + val n = size + val update = arrayOfNulls(n + 1) + array.copyInto(destination = update, endIndex = index) + update[index] = element + array.copyInto(destination = update, destinationOffset = index + 1, startIndex = index, endIndex = n + 1) + array = update + } + + override fun remove(element: E): Boolean { + val index = array.indexOf(element as Any) + if (index == -1) return false + removeAt(index) + return true + } + + override fun removeAt(index: Int): E { + rangeCheck(index) + val n = size + val element = array[index] + val update = arrayOfNulls(n - 1) + array.copyInto(destination = update, endIndex = index) + array.copyInto(destination = update, destinationOffset = index, startIndex = index + 1, endIndex = n) + array = update + return element as E + } + + override fun iterator(): MutableIterator = IteratorImpl(array as Array) + override fun listIterator(): MutableListIterator = throw UnsupportedOperationException("Operation is not supported") + override fun listIterator(index: Int): MutableListIterator = throw UnsupportedOperationException("Operation is not supported") + override fun isEmpty(): Boolean = size == 0 + override fun set(index: Int, element: E): E = throw UnsupportedOperationException("Operation is not supported") + override fun get(index: Int): E = array[rangeCheck(index)] as E + + private class IteratorImpl(private val array: Array) : MutableIterator { + private var current = 0 + + override fun hasNext(): Boolean = current != array.size + + override fun next(): E { + if (!hasNext()) throw NoSuchElementException() + return array[current++] + } + + override fun remove() = throw UnsupportedOperationException("Operation is not supported") + } + + private fun rangeCheck(index: Int) = index.apply { + if (index < 0 || index >= size) throw IndexOutOfBoundsException("index: $index, size: $size") + } +} diff --git a/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt new file mode 100644 index 0000000000..097f4bb607 --- /dev/null +++ b/kotlinx-coroutines-core/wasmJs/src/internal/CoroutineExceptionHandlerImpl.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.internal + +import kotlinx.coroutines.* + +internal actual fun propagateExceptionFinalResort(exception: Throwable) { + // log exception + console.error(exception.toString()) +} \ No newline at end of file diff --git a/kotlinx-coroutines-core/wasmJs/test/PromiseTest.kt b/kotlinx-coroutines-core/wasmJs/test/PromiseTest.kt new file mode 100644 index 0000000000..214f8294cd --- /dev/null +++ b/kotlinx-coroutines-core/wasmJs/test/PromiseTest.kt @@ -0,0 +1,90 @@ +/* + * 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.js.* +import kotlin.test.* + +class PromiseTest : TestBase() { + @Test + fun testPromiseResolvedAsDeferred() = GlobalScope.promise { + val promise = Promise> { resolve, _ -> + resolve("OK".toJsReference()) + } + val deferred = promise.asDeferred>() + assertEquals("OK", deferred.await().get()) + } + + @Test + fun testPromiseRejectedAsDeferred() = GlobalScope.promise { + lateinit var promiseReject: (JsAny) -> Unit + val promise = Promise { _, reject -> + promiseReject = reject + } + val deferred = promise.asDeferred>() + // reject after converting to deferred to avoid "Unhandled promise rejection" warnings + promiseReject(TestException("Rejected").toJsReference()) + try { + deferred.await() + expectUnreached() + } catch (e: Throwable) { + assertTrue(e is TestException) + assertEquals("Rejected", e.message) + } + } + + @Test + fun testCompletedDeferredAsPromise() = GlobalScope.promise { + val deferred = async(start = CoroutineStart.UNDISPATCHED) { + // completed right away + "OK" + } + val promise = deferred.asPromise() + assertEquals("OK", promise.await()) + } + + @Test + fun testWaitForDeferredAsPromise() = GlobalScope.promise { + val deferred = async { + // will complete later + "OK" + } + val promise = deferred.asPromise() + assertEquals("OK", promise.await()) // await yields main thread to deferred coroutine + } + + @Test + fun testCancellableAwaitPromise() = GlobalScope.promise { + lateinit var r: (JsAny) -> Unit + val toAwait = Promise { resolve, _ -> r = resolve } + val job = launch(start = CoroutineStart.UNDISPATCHED) { + toAwait.await() // suspends + } + job.cancel() // cancel the job + r("fail".toJsString()) // too late, the waiting job was already cancelled + } + + @Test + fun testAsPromiseAsDeferred() = GlobalScope.promise { + val deferred = async { "OK" } + val promise = deferred.asPromise() + val d2 = promise.asDeferred() + assertSame(d2, deferred) + assertEquals("OK", d2.await()) + } + + @Test + fun testLeverageTestResult(): TestResult { + // Cannot use expect(..) here + var seq = 0 + val result = runTest { + ++seq + } + return result.then { + if (seq != 1) error("Unexpected result: $seq") + null + } + } +} diff --git a/kotlinx-coroutines-core/wasmJs/test/TestBase.kt b/kotlinx-coroutines-core/wasmJs/test/TestBase.kt new file mode 100644 index 0000000000..08aedb7414 --- /dev/null +++ b/kotlinx-coroutines-core/wasmJs/test/TestBase.kt @@ -0,0 +1,143 @@ +/* + * 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.js.* + +public actual val isStressTest: Boolean = false +public actual val stressTestMultiplier: Int = 1 +public actual val stressTestMultiplierSqrt: Int = 1 + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_TO_CLASS_WITH_DECLARATION_SITE_VARIANCE") +public actual typealias TestResult = Promise + +public actual val isNative = false + +@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") +public actual open class TestBase actual constructor() { + public actual val isBoundByJsTestTimeout = true + private var actionIndex = 0 + private var finished = false + private var error: Throwable? = null + private var lastTestPromise: Promise? = null + + /** + * Throws [IllegalStateException] like `error` in stdlib, but also ensures that the test will not + * complete successfully even if this exception is consumed somewhere in the test. + */ + @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") + public actual fun error(message: Any, cause: Throwable? = null): Nothing { + if (cause != null) println(cause) + val exception = IllegalStateException( + if (cause == null) message.toString() else "$message; caused by $cause") + if (error == null) error = exception + throw exception + } + + private fun printError(message: String, cause: Throwable) { + if (error == null) error = cause + println("$message: $cause") + println(cause) + } + + /** + * Asserts that this invocation is `index`-th in the execution sequence (counting from one). + */ + public actual fun expect(index: Int) { + val wasIndex = ++actionIndex + check(index == wasIndex) { "Expecting action index $index but it is actually $wasIndex" } + } + + /** + * Asserts that this line is never executed. + */ + public actual fun expectUnreached() { + error("Should not be reached") + } + + /** + * Asserts that this it the last action in the test. It must be invoked by any test that used [expect]. + */ + public actual fun finish(index: Int) { + expect(index) + check(!finished) { "Should call 'finish(...)' at most once" } + finished = true + } + + /** + * Asserts that [finish] was invoked + */ + public actual fun ensureFinished() { + require(finished) { "finish(...) should be caller prior to this check" } + } + + public actual fun reset() { + check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" } + actionIndex = 0 + finished = false + } + + @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") + public actual fun runTest( + expected: ((Throwable) -> Boolean)? = null, + unhandled: List<(Throwable) -> Boolean> = emptyList(), + block: suspend CoroutineScope.() -> Unit + ): TestResult { + var exCount = 0 + var ex: Throwable? = null + /* + * This is an additional sanity check against `runTest` mis-usage on JS. + * The only way to write an async test on JS is to return Promise from the test function. + * _Just_ launching promise and returning `Unit` won't suffice as the underlying test framework + * won't be able to detect an asynchronous failure in a timely manner. + * We cannot detect such situations, but we can detect the most common erroneous pattern + * in our code base, an attempt to use multiple `runTest` in the same `@Test` method, + * which typically is a premise to the same error: + * ``` + * @Test + * fun incorrectTestForJs() { // <- promise is not returned + * for (parameter in parameters) { + * runTest { + * runTestForParameter(parameter) + * } + * } + * } + * ``` + */ + if (lastTestPromise != null) { + error("Attempt to run multiple asynchronous test within one @Test method") + } + val result = GlobalScope.promise(block = block, context = CoroutineExceptionHandler { _, e -> + if (e is CancellationException) return@CoroutineExceptionHandler // are ignored + exCount++ + when { + exCount > unhandled.size -> + printError("Too many unhandled exceptions $exCount, expected ${unhandled.size}, got: $e", e) + !unhandled[exCount - 1](e) -> + printError("Unhandled exception was unexpected: $e", e) + } + }).catch { jsE -> + val e = jsE.toThrowableOrNull() ?: error("Unexpected non-Kotlin exception $jsE") + ex = e + if (expected != null) { + if (!expected(e)) + error("Unexpected exception", e) + } else + throw e + + null + }.finally { + if (ex == null && expected != null) error("Exception was expected but none produced") + if (exCount < unhandled.size) + error("Too few unhandled exceptions $exCount, expected ${unhandled.size}") + error?.let { throw it } + check(actionIndex == 0 || finished) { "Expecting that 'finish(...)' was invoked, but it was not" } + } + lastTestPromise = result + return result + } +} + +public actual val isJavaAndWindows: Boolean get() = false \ No newline at end of file diff --git a/kotlinx-coroutines-test/build.gradle.kts b/kotlinx-coroutines-test/build.gradle.kts index 981e04ad64..220c65019c 100644 --- a/kotlinx-coroutines-test/build.gradle.kts +++ b/kotlinx-coroutines-test/build.gradle.kts @@ -26,4 +26,15 @@ kotlin { } } } -} + + wasmJs { + nodejs { + testTask { + filter.apply { + // https://youtrack.jetbrains.com/issue/KT-61888 + excludeTest("TestDispatchersTest", "testMainMocking") + } + } + } + } +} \ No newline at end of file diff --git a/kotlinx-coroutines-test/wasmJs/src/TestBuilders.kt b/kotlinx-coroutines-test/wasmJs/src/TestBuilders.kt new file mode 100644 index 0000000000..898750b8bd --- /dev/null +++ b/kotlinx-coroutines-test/wasmJs/src/TestBuilders.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.test +import kotlinx.coroutines.* +import kotlin.js.* + +@Suppress("ACTUAL_WITHOUT_EXPECT", "ACTUAL_TYPE_ALIAS_TO_CLASS_WITH_DECLARATION_SITE_VARIANCE") +public actual typealias TestResult = Promise + +internal actual fun systemPropertyImpl(name: String): String? = null + +internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() -> Unit): TestResult = + GlobalScope.promise { + testProcedure() + } + +internal actual fun dumpCoroutines() { } \ No newline at end of file diff --git a/kotlinx-coroutines-test/wasmJs/src/internal/TestMainDispatcher.kt b/kotlinx-coroutines-test/wasmJs/src/internal/TestMainDispatcher.kt new file mode 100644 index 0000000000..4d865f83c0 --- /dev/null +++ b/kotlinx-coroutines-test/wasmJs/src/internal/TestMainDispatcher.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.test.internal +import kotlinx.coroutines.* + +@Suppress("INVISIBLE_MEMBER") +internal actual fun Dispatchers.getTestMainDispatcher(): TestMainDispatcher = + when (val mainDispatcher = Main) { + is TestMainDispatcher -> mainDispatcher + else -> TestMainDispatcher(mainDispatcher).also { injectMain(it) } + } diff --git a/kotlinx-coroutines-test/wasmJs/test/Helpers.kt b/kotlinx-coroutines-test/wasmJs/test/Helpers.kt new file mode 100644 index 0000000000..1a8d63d7a7 --- /dev/null +++ b/kotlinx-coroutines-test/wasmJs/test/Helpers.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.test + +import kotlin.test.* + +actual fun testResultChain(block: () -> TestResult, after: (Result) -> TestResult): TestResult = + block().then( + { + after(Result.success(Unit)) + null + }, { + after(Result.failure(it.toThrowableOrNull() ?: Throwable("Unexpected non-Kotlin exception $it"))) + null + }) + +actual typealias NoJs = Ignore \ No newline at end of file diff --git a/kotlinx-coroutines-test/wasmJs/test/PromiseTest.kt b/kotlinx-coroutines-test/wasmJs/test/PromiseTest.kt new file mode 100644 index 0000000000..087db81eb8 --- /dev/null +++ b/kotlinx-coroutines-test/wasmJs/test/PromiseTest.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package kotlinx.coroutines.test + +import kotlinx.coroutines.* +import kotlin.test.* + +class PromiseTest { + @Test + fun testCompletionFromPromise() = runTest { + var promiseEntered = false + val p = promise { + delay(1) + promiseEntered = true + } + delay(2) + p.await() + assertTrue(promiseEntered) + } +} \ No newline at end of file From c39f92ff2ed4a3198c2781de3a9ec9effc3aa223 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 1 Dec 2023 14:11:27 +0100 Subject: [PATCH 52/53] Make the benchmark instructions version-independent --- kotlinx-coroutines-core/jvmBenchmark/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvmBenchmark/README.md b/kotlinx-coroutines-core/jvmBenchmark/README.md index 78eeb6c01d..a89761a245 100644 --- a/kotlinx-coroutines-core/jvmBenchmark/README.md +++ b/kotlinx-coroutines-core/jvmBenchmark/README.md @@ -11,5 +11,5 @@ and decide on their usability. ``` ./gradlew :kotlinx-coroutines-core:jvmBenchmarkBenchmarkJar -java -jar kotlinx-coroutines-core/build/benchmarks/jvmBenchmark/jars/kotlinx-coroutines-core-jvmBenchmark-jmh-1.7.2-SNAPSHOT-JMH.jar +java -jar kotlinx-coroutines-core/build/benchmarks/jvmBenchmark/jars/kotlinx-coroutines-core-jvmBenchmark-jmh-*-JMH.jar ``` From 69dc487d76471adf8be2bbd5ec90a5d23a1c6c33 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 1 Dec 2023 14:28:17 +0100 Subject: [PATCH 53/53] Version 1.8.0-RC --- CHANGES.md | 15 +++++++++++++++ README.md | 12 ++++++------ gradle.properties | 2 +- integration-testing/gradle.properties | 2 +- kotlinx-coroutines-debug/README.md | 2 +- kotlinx-coroutines-test/README.md | 2 +- ui/coroutines-guide-ui.md | 2 +- 7 files changed, 26 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4ac7156672..dc14a5e99a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,20 @@ # Change log for kotlinx.coroutines +## Version 1.8.0-RC + +* Implement the library for the Web Assembly (Wasm) for JavaScript (#3713). Thanks @igoriakovlev! +* On Android, ensure that `Dispatchers.Main != Dispatchers.Main.immediate` (#3545, #3963). +* `kotlinx-coroutines-debug` is published with the incorrect Java 9 module info (#3944). +* Major Kotlin version update: was 1.8.20, became 1.9.21. +* `kotlinx-coroutines-test`: set the default timeout of `runTest` to 60 seconds, added the ability to configure it on the JVM with the `kotlinx.coroutines.test.default_timeout=10s` (#3800). +* `kotlinx-coroutines-test`: fixed a bug that could lead to not all uncaught exceptions being reported after some tests failed (#3800). +* `delay(Duration)` rounds nanoseconds up to whole milliseconds and not down (#3920). Thanks @kevincianfarini! +* `Dispatchers.Default` and the default thread for background work are guaranteed to use the same context classloader as the object containing it them (#3832). +* It is guaranteed that by the time `SharedFlow.collect` suspends for the first time, it's registered as a subscriber for that `SharedFlow` (#3885). Before, it was also true, but not documented. +* Atomicfu version is updated to 0.23.1, and Kotlin/Native atomic transformations are enabled, reducing the footprint of coroutine-heavy code (#3954). +* Added a workaround for miscompilation of `withLock` on JS (#3881). Thanks @CLOVIS-AI! +* Small tweaks and documentation fixes. + ## Version 1.7.3 * Disabled the publication of the multiplatform library metadata for the old (1.6 and earlier) KMP Gradle plugin (#3809). diff --git a/README.md b/README.md index 8f89a6df2d..a053a0df1a 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.3)](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.3) +[![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.8.0-RC)](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.8.0-RC) [![Kotlin](https://img.shields.io/badge/kotlin-1.9.21-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.3 + 1.8.0-RC ``` @@ -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.3") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0-RC") } ``` @@ -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.3") +implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC") ``` 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.3") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0-RC") } } ``` @@ -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.3) +[`kotlinx-coroutines-core-js`](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.8.0-RC) (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/gradle.properties b/gradle.properties index 6d46042046..ee5891fc7a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ # # Kotlin -version=1.7.3-SNAPSHOT +version=1.8.0-RC-SNAPSHOT group=org.jetbrains.kotlinx kotlin_version=1.9.21 diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties index 5cc832137e..f74fc7d901 100644 --- a/integration-testing/gradle.properties +++ b/integration-testing/gradle.properties @@ -1,5 +1,5 @@ kotlin_version=1.9.21 -coroutines_version=1.7.3-SNAPSHOT +coroutines_version=1.8.0-RC-SNAPSHOT asm_version=9.3 kotlin.code.style=official diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md index 5e385e3c24..a0d6cbafe3 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.3.jar`. +You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.8.0-RC.jar`. Additionally, on Linux and Mac OS X you can use `kill -5 $pid` command in order to force your application to print all alive coroutines. 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-test/README.md b/kotlinx-coroutines-test/README.md index f5dd8e6cb5..6acd168e24 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.3' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0-RC' } ``` diff --git a/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md index 872926d713..a1f442ec8b 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.3" +implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0-RC" ``` You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your