diff --git a/CHANGES.md b/CHANGES.md index e6dd931db3..2c1beec875 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # Change log for kotlinx.coroutines +## Version 1.0.0 + + * All Kotlin dependencies updated to 1.3 release version. + * Fixed potential memory leak in `HandlerDispatcher.scheduleResumeAfterDelay`, thanks @cbeyls. + * `yield` support for `Unconfined` and immediate dispatchers (#737). + * Various documentation improvements. + ## Version 1.0.0-RC1 * Coroutines API is updated to Kotlin 1.3. diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 1b393ee573..b7bcf0c4cf 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -20,6 +20,6 @@ In order to migrate `kotlinx.coroutines` to `1.0.0`, follow these steps: 1. Update `kotlinx.coroutines` to `0.30.2` version. 2. Inspect compiler warnings about deprecated API and migrate it to a proposed alternative. Most of deprecated API has a corresponding replacement which can be applied from IDEA with quickfix. -3. Update Kotlin version to `1.3.0` or to the latest `1.3.0-rc` and `kotlinx.coroutines` to version `0.30.2-eap13`. Then just get rid of `experimental` suffix in all imports. +3. Update Kotlin version to `1.3.0` and `kotlinx.coroutines` to version `0.30.2-eap13`. Then just get rid of `experimental` suffix in all imports. 4. Update `kotlinx.coroutines` to version `1.0.0` or to the corresponding release candidate of it). \ No newline at end of file diff --git a/README.md b/README.md index 3856e6201a..8cc87c8062 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ [![official JetBrains project](http://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) -[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=1.0.0-RC1) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.0.0-RC1) +[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=1.0.0) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.0.0) Library support for Kotlin coroutines with [multiplatform](#multiplatform) support. -This is a companion version for Kotlin `1.3.0-rc-146` release. +This is a companion version for Kotlin `1.3.0` release. **NOTE**: `0.30.2` was the last release with Kotlin 1.2 and experimental coroutines. See [COMPATIBILITY.md](COMPATIBILITY.md) for details of migration onto the stable Kotlin 1.3 coroutines. @@ -69,7 +69,7 @@ Add dependencies (you can also add other modules that you need): org.jetbrains.kotlinx kotlinx-coroutines-core - 1.0.0-RC1 + 1.0.0 ``` @@ -77,19 +77,17 @@ And make sure that you use the latest Kotlin version: ```xml - 1.3.0-rc-146 + 1.3.0 ``` -While Kotlin 1.3 is still in release candidate status, in order to depend on it you should add eap repository: `https://dl.bintray.com/kotlin/kotlin-eap`. - ### Gradle Add dependencies (you can also add other modules that you need): ```groovy dependencies { - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0-RC1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0' } ``` @@ -97,7 +95,7 @@ And make sure that you use the latest Kotlin version: ```groovy buildscript { - ext.kotlin_version = '1.3.0-rc-146' + ext.kotlin_version = '1.3.0' } ``` @@ -115,7 +113,7 @@ Add dependencies (you can also add other modules that you need): ```groovy dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0-RC1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0") } ``` @@ -123,19 +121,11 @@ And make sure that you use the latest Kotlin version: ```groovy plugins { - kotlin("jvm") version "1.3.0-rc-146" + kotlin("jvm") version "1.3.0" } ``` Make sure that you have either `jcenter()` or `mavenCentral()` in the list of repositories. -For Kotlin EAP builds you also may need `kotlin-eap` repository: - -``` -repository { - jcenter() - maven { url "https://kotlin.bintray.com/kotlin-eap" } -} -``` ### Multiplatform @@ -151,7 +141,7 @@ Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android) module as dependency when using `kotlinx.coroutines` on Android: ```groovy -implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0-RC1' +implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0' ``` This gives you access to Android [Dispatchers.Main](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-android/kotlinx.coroutines.android/kotlinx.coroutines.-dispatchers/index.html) coroutine dispatcher and also makes sure that in case of crashed coroutine with unhandled exception this diff --git a/build.gradle b/build.gradle index ce77c0ad4a..9013e17cdc 100644 --- a/build.gradle +++ b/build.gradle @@ -52,8 +52,17 @@ allprojects { kotlin_version = '1.2-SNAPSHOT' } + def name = it.name repositories { + /* + * google should be first in the repository list because some of the play services + * transitive dependencies was removed from jcenter, thus breaking gradle dependency resolution + */ + if (name == "kotlinx-coroutines-play-services") { + google() + } jcenter() + maven { url "https://kotlin.bintray.com/kotlin-dev" } maven { url "https://kotlin.bintray.com/kotlin-eap" } maven { url "https://kotlin.bintray.com/kotlinx" } } diff --git a/common/kotlinx-coroutines-core-common/src/Dispatched.kt b/common/kotlinx-coroutines-core-common/src/Dispatched.kt index 32ee51f0a2..d7f7a97717 100644 --- a/common/kotlinx-coroutines-core-common/src/Dispatched.kt +++ b/common/kotlinx-coroutines-core-common/src/Dispatched.kt @@ -21,26 +21,40 @@ internal object UndispatchedEventLoop { @JvmField internal val threadLocalEventLoop = CommonThreadLocal { EventLoop() } - inline fun execute(continuation: DispatchedContinuation<*>, contState: Any?, mode: Int, block: () -> Unit) { + /** + * Executes given [block] as part of current event loop, updating related to block [continuation] + * mode and state if continuation is not resumed immediately. + * [doYield] indicates whether current continuation is yielding (to provide fast-path if event-loop is empty). + * Returns `true` if execution of continuation was queued (trampolined) or `false` otherwise. + */ + inline fun execute(continuation: DispatchedContinuation<*>, contState: Any?, mode: Int, + doYield: Boolean = false, block: () -> Unit) : Boolean { val eventLoop = threadLocalEventLoop.get() if (eventLoop.isActive) { + // If we are yielding and queue is empty, we can bail out as part of fast path + if (doYield && eventLoop.queue.isEmpty) { + return false + } + continuation._state = contState continuation.resumeMode = mode eventLoop.queue.addLast(continuation) - return + return true } runEventLoop(eventLoop, block) + return false } - fun resumeUndispatched(task: DispatchedTask<*>) { + fun resumeUndispatched(task: DispatchedTask<*>): Boolean { val eventLoop = threadLocalEventLoop.get() if (eventLoop.isActive) { eventLoop.queue.addLast(task) - return + return true } runEventLoop(eventLoop, { task.resume(task.delegate, MODE_UNDISPATCHED) }) + return false } inline fun runEventLoop(eventLoop: EventLoop, block: () -> Unit) { @@ -227,6 +241,11 @@ internal interface DispatchedTask : Runnable { } } +internal fun DispatchedContinuation.yieldUndispatched(): Boolean = + UndispatchedEventLoop.execute(this, Unit, MODE_CANCELLABLE, doYield = true) { + run() + } + internal fun DispatchedTask.dispatch(mode: Int = MODE_CANCELLABLE) { val delegate = this.delegate if (mode.isDispatchedMode && delegate is DispatchedContinuation<*> && mode.isCancellableMode == resumeMode.isCancellableMode) { diff --git a/common/kotlinx-coroutines-core-common/src/Yield.kt b/common/kotlinx-coroutines-core-common/src/Yield.kt index 632dcba0b0..78ab27fb87 100644 --- a/common/kotlinx-coroutines-core-common/src/Yield.kt +++ b/common/kotlinx-coroutines-core-common/src/Yield.kt @@ -19,7 +19,9 @@ public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { u val context = uCont.context context.checkCompletion() val cont = uCont.intercepted() as? DispatchedContinuation ?: return@sc Unit - if (!cont.dispatcher.isDispatchNeeded(context)) return@sc Unit + if (!cont.dispatcher.isDispatchNeeded(context)) { + return@sc if (cont.yieldUndispatched()) COROUTINE_SUSPENDED else Unit + } cont.dispatchYield(Unit) COROUTINE_SUSPENDED } diff --git a/common/kotlinx-coroutines-core-common/src/channels/Channel.kt b/common/kotlinx-coroutines-core-common/src/channels/Channel.kt index 81dc89c7ac..8cf8572aba 100644 --- a/common/kotlinx-coroutines-core-common/src/channels/Channel.kt +++ b/common/kotlinx-coroutines-core-common/src/channels/Channel.kt @@ -210,10 +210,9 @@ public interface ReceiveChannel { * **Note: This is an obsolete api.** * This function will be replaced with `receiveOrClosed: ReceiveResult` and * extension `suspend fun ReceiveChannel.receiveOrNull(): E?` + * It is obsolete because it does not distinguish closed channel and null elements. */ - @ExperimentalCoroutinesApi @ObsoleteCoroutinesApi - @Deprecated(level = DeprecationLevel.WARNING, message = "This method does not distinguish closed channel and null elements") public suspend fun receiveOrNull(): E? /** diff --git a/common/kotlinx-coroutines-core-common/src/internal/ArrayQueue.kt b/common/kotlinx-coroutines-core-common/src/internal/ArrayQueue.kt index a6bf8f6180..5cfb8e8df1 100644 --- a/common/kotlinx-coroutines-core-common/src/internal/ArrayQueue.kt +++ b/common/kotlinx-coroutines-core-common/src/internal/ArrayQueue.kt @@ -8,6 +8,7 @@ internal class ArrayQueue { private var elements = arrayOfNulls(16) private var head = 0 private var tail = 0 + val isEmpty: Boolean get() = head == tail public fun addLast(element: T) { elements[tail] = element diff --git a/common/kotlinx-coroutines-core-common/test/UnconfinedTest.kt b/common/kotlinx-coroutines-core-common/test/UnconfinedTest.kt index 8866057a09..f37c35657c 100644 --- a/common/kotlinx-coroutines-core-common/test/UnconfinedTest.kt +++ b/common/kotlinx-coroutines-core-common/test/UnconfinedTest.kt @@ -54,7 +54,7 @@ class UnconfinedTest : TestBase() { } @Test - fun enterMultipleTimes() = runTest { + fun testEnterMultipleTimes() = runTest { launch(Unconfined) { expect(1) } @@ -70,5 +70,46 @@ class UnconfinedTest : TestBase() { finish(4) } + @Test + fun testYield() = runTest { + expect(1) + launch(Dispatchers.Unconfined) { + expect(2) + yield() + launch { + expect(4) + } + expect(3) + yield() + expect(5) + }.join() + + finish(6) + } + + @Test + fun testCancellationWihYields() = runTest { + expect(1) + GlobalScope.launch(Dispatchers.Unconfined) { + val job = coroutineContext[Job]!! + expect(2) + yield() + GlobalScope.launch(Dispatchers.Unconfined) { + expect(4) + job.cancel() + expect(5) + } + expect(3) + + try { + yield() + } finally { + expect(6) + } + } + + finish(7) + } + class TestException : Throwable() } diff --git a/core/kotlinx-coroutines-core/test/UnconfinedConcurrentStressTest.kt b/core/kotlinx-coroutines-core/test/UnconfinedConcurrentStressTest.kt index 4fe1fd84aa..03088006f1 100644 --- a/core/kotlinx-coroutines-core/test/UnconfinedConcurrentStressTest.kt +++ b/core/kotlinx-coroutines-core/test/UnconfinedConcurrentStressTest.kt @@ -19,9 +19,9 @@ class UnconfinedConcurrentStressTest : TestBase() { executor.close() } - @Test(timeout = 10_000L) + @Test fun testConcurrent() = runTest { - val iterations = 10_000 * stressTestMultiplier + val iterations = 1_000 * stressTestMultiplier val startBarrier = CyclicBarrier(threads + 1) val finishLatch = CountDownLatch(threads) diff --git a/gradle.properties b/gradle.properties index 775676ae54..9240b25cb7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,12 +1,12 @@ # Kotlin -version=1.0.0-RC1-SNAPSHOT +version=1.0.0-SNAPSHOT group=org.jetbrains.kotlinx -kotlin_version=1.3.0-rc-146 -kotlin_native_version=1.3.0-rc-146 +kotlin_version=1.3.0 +kotlin_native_version=1.3.0-rc-208 # Dependencies junit_version=4.12 -atomicFU_version=0.11.11 +atomicFU_version=0.11.12 html_version=0.6.8 lincheck_version=1.9 dokka_version=0.9.16-rdev-2-mpp-hacks diff --git a/integration/kotlinx-coroutines-play-services/build.gradle b/integration/kotlinx-coroutines-play-services/build.gradle index 44eec3bf2a..51cce3f278 100644 --- a/integration/kotlinx-coroutines-play-services/build.gradle +++ b/integration/kotlinx-coroutines-play-services/build.gradle @@ -9,10 +9,6 @@ import java.util.zip.ZipFile ext.tasks_version = '15.0.1' -repositories { - google() -} - def attr = Attribute.of("artifactType", String.class) configurations { aar { diff --git a/native/README.md b/native/README.md index ce0dfa135e..55a5019281 100644 --- a/native/README.md +++ b/native/README.md @@ -42,7 +42,7 @@ repositories { } dependencies { - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.0.0-RC1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core-native:1.0.0' } sourceSets { diff --git a/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md index c2c8232291..f8818cf7ff 100644 --- a/ui/coroutines-guide-ui.md +++ b/ui/coroutines-guide-ui.md @@ -165,7 +165,7 @@ Add dependencies on `kotlinx-coroutines-android` module to the `dependencies { . `app/build.gradle` file: ```groovy -compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0-RC1" +compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0" ``` You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your diff --git a/ui/kotlinx-coroutines-android/animation-app/gradle.properties b/ui/kotlinx-coroutines-android/animation-app/gradle.properties index c76bcf9f19..1d37dbb738 100644 --- a/ui/kotlinx-coroutines-android/animation-app/gradle.properties +++ b/ui/kotlinx-coroutines-android/animation-app/gradle.properties @@ -18,6 +18,6 @@ org.gradle.jvmargs=-Xmx1536m kotlin.coroutines=enable -kotlin_version=1.3.0-rc-146 -coroutines_version=1.0.0-RC1 +kotlin_version=1.3.0 +coroutines_version=1.0.0 diff --git a/ui/kotlinx-coroutines-android/example-app/gradle.properties b/ui/kotlinx-coroutines-android/example-app/gradle.properties index c76bcf9f19..1d37dbb738 100644 --- a/ui/kotlinx-coroutines-android/example-app/gradle.properties +++ b/ui/kotlinx-coroutines-android/example-app/gradle.properties @@ -18,6 +18,6 @@ org.gradle.jvmargs=-Xmx1536m kotlin.coroutines=enable -kotlin_version=1.3.0-rc-146 -coroutines_version=1.0.0-RC1 +kotlin_version=1.3.0 +coroutines_version=1.0.0 diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt index 2181b1cda4..be5185df83 100644 --- a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt +++ b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt @@ -120,9 +120,11 @@ internal class HandlerContext private constructor( } override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { - handler.postDelayed({ + val block = Runnable { with(continuation) { resumeUndispatched(Unit) } - }, timeMillis.coerceAtMost(MAX_DELAY)) + } + handler.postDelayed(block, timeMillis.coerceAtMost(MAX_DELAY)) + continuation.invokeOnCancellation { handler.removeCallbacks(block) } } override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {