From 937cc0c4912ad707850b0f37417164a77525f4b7 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 13 Nov 2020 00:49:56 -0800 Subject: [PATCH 01/15] Fix replacement for deprecated broadcastIn operator (#2382) Fixes #2344 --- kotlinx-coroutines-core/common/src/flow/Channels.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/flow/Channels.kt b/kotlinx-coroutines-core/common/src/flow/Channels.kt index 762cdcad1b..63b285abc3 100644 --- a/kotlinx-coroutines-core/common/src/flow/Channels.kt +++ b/kotlinx-coroutines-core/common/src/flow/Channels.kt @@ -201,7 +201,7 @@ public fun BroadcastChannel.asFlow(): Flow = flow { */ @Deprecated( message = "Use shareIn operator and the resulting SharedFlow as a replacement for BroadcastChannel", - replaceWith = ReplaceWith("shareIn(scope, 0, SharingStarted.Lazily)"), + replaceWith = ReplaceWith("this.shareIn(scope, SharingStarted.Lazily, 0)"), level = DeprecationLevel.WARNING ) public fun Flow.broadcastIn( From addff4b4b7265166c608642a74d21d8fe9a7c1b4 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 13 Nov 2020 03:11:47 -0800 Subject: [PATCH 02/15] Prevent potential re-park when the unparking thread is too slow (#2381) --- .../jvm/src/scheduling/CoroutineScheduler.kt | 14 +++++++++++++- ...ngCoroutineDispatcherMixedStealingStressTest.kt | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt index 62cf80f7f8..ad61224b52 100644 --- a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt +++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt @@ -721,7 +721,19 @@ internal class CoroutineScheduler( } assert { localQueue.size == 0 } workerCtl.value = PARKED // Update value once - while (inStack()) { // Prevent spurious wakeups + /* + * inStack() prevents spurious wakeups, while workerCtl.value == PARKED + * prevents the following race: + * + * - T2 scans the queue, adds itself to the stack, goes to rescan + * - T2 suspends in 'workerCtl.value = PARKED' line + * - T1 pops T2 from the stack, claims workerCtl, suspends + * - T2 fails 'while (inStack())' check, goes to full rescan + * - T2 adds itself to the stack, parks + * - T1 unparks T2, bails out with success + * - T2 unparks and loops in 'while (inStack())' + */ + while (inStack() && workerCtl.value == PARKED) { // Prevent spurious wakeups if (isTerminated || state == WorkerState.TERMINATED) break tryReleaseCpu(WorkerState.PARKING) interrupted() // Cleanup interruptions diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherMixedStealingStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherMixedStealingStressTest.kt index 1fe0d8386d..3a55f8c4f2 100644 --- a/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherMixedStealingStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/scheduling/BlockingCoroutineDispatcherMixedStealingStressTest.kt @@ -77,4 +77,4 @@ class BlockingCoroutineDispatcherMixedStealingStressTest : SchedulerTestBase() { cpuBlocker.await() } } -} \ No newline at end of file +} From 37b95a94816297667b9d4b537688c3a9e174ca8b Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Sat, 14 Nov 2020 18:29:03 +0300 Subject: [PATCH 03/15] Simplify JobNode.toString to reduce code and avoid potential StackOverflow (#2377) Fixes #2371 --- kotlinx-coroutines-core/common/src/JobSupport.kt | 13 +------------ .../common/src/selects/Select.kt | 1 - kotlinx-coroutines-core/jvm/src/Future.kt | 1 - 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/JobSupport.kt b/kotlinx-coroutines-core/common/src/JobSupport.kt index 020d00a32c..5f21299e58 100644 --- a/kotlinx-coroutines-core/common/src/JobSupport.kt +++ b/kotlinx-coroutines-core/common/src/JobSupport.kt @@ -1151,8 +1151,6 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren override fun invoke(cause: Throwable?) { parent.continueCompleting(state, child, proposedUpdate) } - override fun toString(): String = - "ChildCompletion[$child, $proposedUpdate]" } private class AwaitContinuation( @@ -1350,6 +1348,7 @@ internal abstract class JobNode( override val isActive: Boolean get() = true override val list: NodeList? get() = null override fun dispose() = (job as JobSupport).removeNode(this) + override fun toString() = "$classSimpleName@$hexAddress[job@${job.hexAddress}]" } internal class NodeList : LockFreeLinkedListHead(), Incomplete { @@ -1384,7 +1383,6 @@ private class InvokeOnCompletion( private val handler: CompletionHandler ) : JobNode(job) { override fun invoke(cause: Throwable?) = handler.invoke(cause) - override fun toString() = "InvokeOnCompletion[$classSimpleName@$hexAddress]" } private class ResumeOnCompletion( @@ -1392,7 +1390,6 @@ private class ResumeOnCompletion( private val continuation: Continuation ) : JobNode(job) { override fun invoke(cause: Throwable?) = continuation.resume(Unit) - override fun toString() = "ResumeOnCompletion[$continuation]" } private class ResumeAwaitOnCompletion( @@ -1411,7 +1408,6 @@ private class ResumeAwaitOnCompletion( continuation.resume(state.unboxState() as T) } } - override fun toString() = "ResumeAwaitOnCompletion[$continuation]" } internal class DisposeOnCompletion( @@ -1419,7 +1415,6 @@ internal class DisposeOnCompletion( private val handle: DisposableHandle ) : JobNode(job) { override fun invoke(cause: Throwable?) = handle.dispose() - override fun toString(): String = "DisposeOnCompletion[$handle]" } private class SelectJoinOnCompletion( @@ -1431,7 +1426,6 @@ private class SelectJoinOnCompletion( if (select.trySelect()) block.startCoroutineCancellable(select.completion) } - override fun toString(): String = "SelectJoinOnCompletion[$select]" } private class SelectAwaitOnCompletion( @@ -1443,7 +1437,6 @@ private class SelectAwaitOnCompletion( if (select.trySelect()) job.selectAwaitCompletion(select, block) } - override fun toString(): String = "SelectAwaitOnCompletion[$select]" } // -------- invokeOnCancellation nodes @@ -1463,7 +1456,6 @@ private class InvokeOnCancelling( override fun invoke(cause: Throwable?) { if (_invoked.compareAndSet(0, 1)) handler.invoke(cause) } - override fun toString() = "InvokeOnCancelling[$classSimpleName@$hexAddress]" } internal class ChildHandleNode( @@ -1472,7 +1464,6 @@ internal class ChildHandleNode( ) : JobCancellingNode(parent), ChildHandle { override fun invoke(cause: Throwable?) = childJob.parentCancelled(job) override fun childCancelled(cause: Throwable): Boolean = job.childCancelled(cause) - override fun toString(): String = "ChildHandle[$childJob]" } // Same as ChildHandleNode, but for cancellable continuation @@ -1483,7 +1474,5 @@ internal class ChildContinuation( override fun invoke(cause: Throwable?) { child.parentCancelled(child.getContinuationCancellationCause(job)) } - override fun toString(): String = - "ChildContinuation[$child]" } diff --git a/kotlinx-coroutines-core/common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt index 99c54f8417..81d3745e62 100644 --- a/kotlinx-coroutines-core/common/src/selects/Select.kt +++ b/kotlinx-coroutines-core/common/src/selects/Select.kt @@ -339,7 +339,6 @@ internal class SelectBuilderImpl( if (trySelect()) resumeSelectWithException(job.getCancellationException()) } - override fun toString(): String = "SelectOnCancelling[${this@SelectBuilderImpl}]" } @PublishedApi diff --git a/kotlinx-coroutines-core/jvm/src/Future.kt b/kotlinx-coroutines-core/jvm/src/Future.kt index bd16f49af0..58792ced31 100644 --- a/kotlinx-coroutines-core/jvm/src/Future.kt +++ b/kotlinx-coroutines-core/jvm/src/Future.kt @@ -41,7 +41,6 @@ private class CancelFutureOnCompletion( // interruption flag and it will cause spurious failures elsewhere future.cancel(false) } - override fun toString() = "CancelFutureOnCompletion[$future]" } private class CancelFutureOnCancel(private val future: Future<*>) : CancelHandler() { From dede17ebea4528679aa9b473af59728734336fb3 Mon Sep 17 00:00:00 2001 From: Will Buck Date: Fri, 13 Nov 2020 20:20:16 -0600 Subject: [PATCH 04/15] Don't unconditionally cast uCont in ScopeCoroutine cast to CoroutineStackFrame Fixes #2386 --- kotlinx-coroutines-core/common/src/internal/Scopes.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/internal/Scopes.kt b/kotlinx-coroutines-core/common/src/internal/Scopes.kt index 9bb2ce3d29..f9362cff11 100644 --- a/kotlinx-coroutines-core/common/src/internal/Scopes.kt +++ b/kotlinx-coroutines-core/common/src/internal/Scopes.kt @@ -16,7 +16,7 @@ internal open class ScopeCoroutine( context: CoroutineContext, @JvmField val uCont: Continuation // unintercepted continuation ) : AbstractCoroutine(context, true), CoroutineStackFrame { - final override val callerFrame: CoroutineStackFrame? get() = uCont as CoroutineStackFrame? + final override val callerFrame: CoroutineStackFrame? get() = uCont as? CoroutineStackFrame final override fun getStackTraceElement(): StackTraceElement? = null final override val isScopedCoroutine: Boolean get() = true From bc553ba7c5aeb3a7fddad23e47b2e11e9de8f297 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 16 Nov 2020 11:23:41 -0800 Subject: [PATCH 05/15] Conditionally create an instance of CancellationException in Channel.cancel() (#2384) Avoid creating costly exception when the channel is cancelled to save a few cycles when it's not necessary. Cancellation is heavy-enough when the channel is open, so the single check won't worsen it. --- kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt | 1 + kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt | 1 + kotlinx-coroutines-core/common/src/flow/internal/Combine.kt | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt index 8edd2b310c..87bd43714d 100644 --- a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt @@ -635,6 +635,7 @@ internal abstract class AbstractChannel( cancelInternal(cause) final override fun cancel(cause: CancellationException?) { + if (isClosedForReceive) return // Do not create an exception if channel is already cancelled cancelInternal(cause ?: CancellationException("$classSimpleName was cancelled")) } diff --git a/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt b/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt index a75d466199..9ceb77ddc2 100644 --- a/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt +++ b/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt @@ -26,6 +26,7 @@ internal open class ChannelCoroutine( } final override fun cancel(cause: CancellationException?) { + if (isClosedForReceive) return // Do not create an exception if channel is already cancelled cancelInternal(cause ?: defaultCancellationException()) } diff --git a/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt b/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt index bbdebd08b9..d276e5100a 100644 --- a/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt +++ b/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt @@ -137,7 +137,7 @@ internal fun zipImpl(flow: Flow, flow2: Flow, transform: sus } catch (e: AbortFlowException) { e.checkOwnership(owner = this@unsafeFlow) } finally { - if (!second.isClosedForReceive) second.cancel() + second.cancel() } } } From 598b86151e849f57238c0c6d1e81d314290279be Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Tue, 17 Nov 2020 14:44:27 +0300 Subject: [PATCH 06/15] Add lint warnings on SharedFlow operations that never complete (#2376) * Add lint warnings on SharedFlow operations that never complete Fixes #2340 Fixes #2368 * ~ remove awaitCancellation replacements --- .../common/src/flow/operators/Lint.kt | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt index 7a70fbf7f2..9aa240d8a9 100644 --- a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt +++ b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt @@ -2,12 +2,13 @@ * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("unused") +@file:Suppress("unused", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") package kotlinx.coroutines.flow import kotlinx.coroutines.* import kotlin.coroutines.* +import kotlin.internal.InlineOnly /** * Applying [cancellable][Flow.cancellable] to a [SharedFlow] has no effect. @@ -79,4 +80,61 @@ public fun FlowCollector<*>.cancel(cause: CancellationException? = null): Unit = replaceWith = ReplaceWith("currentCoroutineContext()") ) public val FlowCollector<*>.coroutineContext: CoroutineContext - get() = noImpl() \ No newline at end of file + get() = noImpl() + +@Deprecated( + message = "SharedFlow never completes, so this operator has no effect.", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this") +) +@InlineOnly +public inline fun SharedFlow.catch(noinline action: suspend FlowCollector.(cause: Throwable) -> Unit): Flow = + (this as Flow).catch(action) + +@Deprecated( + message = "SharedFlow never completes, so this operator has no effect.", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this") +) +@InlineOnly +public inline fun SharedFlow.retry( + retries: Long = Long.MAX_VALUE, + noinline predicate: suspend (cause: Throwable) -> Boolean = { true } +): Flow = + (this as Flow).retry(retries, predicate) + +@Deprecated( + message = "SharedFlow never completes, so this operator has no effect.", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("this") +) +@InlineOnly +public inline fun SharedFlow.retryWhen(noinline predicate: suspend FlowCollector.(cause: Throwable, attempt: Long) -> Boolean): Flow = + (this as Flow).retryWhen(predicate) + +@Suppress("DeprecatedCallableAddReplaceWith") +@Deprecated( + message = "SharedFlow never completes, so this terminal operation never completes.", + level = DeprecationLevel.WARNING +) +@InlineOnly +public suspend inline fun SharedFlow.toList(): List = + (this as Flow).toList() + +@Suppress("DeprecatedCallableAddReplaceWith") +@Deprecated( + message = "SharedFlow never completes, so this terminal operation never completes.", + level = DeprecationLevel.WARNING +) +@InlineOnly +public suspend inline fun SharedFlow.toSet(): Set = + (this as Flow).toSet() + +@Suppress("DeprecatedCallableAddReplaceWith") +@Deprecated( + message = "SharedFlow never completes, so this terminal operation never completes.", + level = DeprecationLevel.WARNING +) +@InlineOnly +public suspend inline fun SharedFlow.count(): Int = + (this as Flow).count() From 8ca5296cd19e586d290d0eae7accf904822aae4d Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Tue, 17 Nov 2020 22:19:09 +0300 Subject: [PATCH 07/15] Make SharingStarted a fun interface (#2397) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was a part of the original design but was forgotten because the prototype was developeв before Kotlin 1.4.0. It makes implementing custom SharingStarted strategies more concise. --- .../common/src/flow/SharingStarted.kt | 12 +++++------- .../common/test/flow/sharing/ShareInTest.kt | 8 +++----- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt b/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt index 19e5fa36c7..c36d633cbc 100644 --- a/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt +++ b/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt @@ -38,7 +38,7 @@ public enum class SharingCommand { /** * A strategy for starting and stopping the sharing coroutine in [shareIn] and [stateIn] operators. * - * This interface provides a set of built-in strategies: [Eagerly], [Lazily], [WhileSubscribed], and + * This functional interface provides a set of built-in strategies: [Eagerly], [Lazily], [WhileSubscribed], and * supports custom strategies by implementing this interface's [command] function. * * For example, it is possible to define a custom strategy that starts the upstream only when the number @@ -46,11 +46,9 @@ public enum class SharingCommand { * that it looks like a built-in strategy on the use-site: * * ``` - * fun SharingStarted.Companion.WhileSubscribedAtLeast(threshold: Int): SharingStarted = - * object : SharingStarted { - * override fun command(subscriptionCount: StateFlow): Flow = - * subscriptionCount - * .map { if (it >= threshold) SharingCommand.START else SharingCommand.STOP } + * fun SharingStarted.Companion.WhileSubscribedAtLeast(threshold: Int) = + * SharingStarted { subscriptionCount: StateFlow -> + * subscriptionCount.map { if (it >= threshold) SharingCommand.START else SharingCommand.STOP } * } * ``` * @@ -74,7 +72,7 @@ public enum class SharingCommand { * The completion of the `command` flow normally has no effect (the upstream flow keeps running if it was running). * The failure of the `command` flow cancels the sharing coroutine and the upstream flow. */ -public interface SharingStarted { +public fun interface SharingStarted { public companion object { /** * Sharing is started immediately and never stops. diff --git a/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt b/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt index 9020f5f311..42cdb1e19f 100644 --- a/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt +++ b/kotlinx-coroutines-core/common/test/flow/sharing/ShareInTest.kt @@ -187,11 +187,9 @@ class ShareInTest : TestBase() { } @Suppress("TestFunctionName") - private fun SharingStarted.Companion.WhileSubscribedAtLeast(threshold: Int): SharingStarted = - object : SharingStarted { - override fun command(subscriptionCount: StateFlow): Flow = - subscriptionCount - .map { if (it >= threshold) SharingCommand.START else SharingCommand.STOP } + private fun SharingStarted.Companion.WhileSubscribedAtLeast(threshold: Int) = + SharingStarted { subscriptionCount -> + subscriptionCount.map { if (it >= threshold) SharingCommand.START else SharingCommand.STOP } } private class FlowState { From 4fe809f80a259ff41a77ba0257e44802907ccb80 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 18 Nov 2020 00:40:50 -0800 Subject: [PATCH 08/15] Unlock Mutex and release Semaphore during cancellation on a fast branch of slow-path in Mutex/Semaphore (#2396) Fixes #2390 Co-authored-by: Gareth Pearce --- .../common/src/sync/Mutex.kt | 3 ++- .../common/src/sync/Semaphore.kt | 7 +++---- .../common/test/sync/MutexTest.kt | 3 ++- .../jvm/test/sync/MutexStressTest.kt | 19 ++++++++++++++++++- .../jvm/test/sync/SemaphoreStressTest.kt | 19 ++++++++++++++++++- 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt index 73aaab5fbf..707c4640bc 100644 --- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt +++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt @@ -201,7 +201,8 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2 { // try lock val update = if (owner == null) EMPTY_LOCKED else Empty(owner) if (_state.compareAndSet(state, update)) { // locked - cont.resume(Unit) + // TODO implement functional type in LockCont as soon as we get rid of legacy JS + cont.resume(Unit) { unlock(owner) } return@sc } } diff --git a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt index 84b7f4f8a2..c342bb3009 100644 --- a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt +++ b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt @@ -172,7 +172,7 @@ private class SemaphoreImpl(private val permits: Int, acquiredPermits: Int) : Se if (addAcquireToQueue(cont)) return@sc val p = _availablePermits.getAndDecrement() if (p > 0) { // permit acquired - cont.resume(Unit) + cont.resume(Unit, onCancellationRelease) return@sc } } @@ -206,9 +206,8 @@ private class SemaphoreImpl(private val permits: Int, acquiredPermits: Int) : Se // On CAS failure -- the cell must be either PERMIT or BROKEN // If the cell already has PERMIT from tryResumeNextFromQueue, try to grab it if (segment.cas(i, PERMIT, TAKEN)) { // took permit thus eliminating acquire/release pair - // The following resume must always succeed, since continuation was not published yet and we don't have - // to pass onCancellationRelease handle, since the coroutine did not suspend yet and cannot be cancelled - cont.resume(Unit) + /// This continuation is not yet published, but still can be cancelled via outer job + cont.resume(Unit, onCancellationRelease) return true } assert { segment.get(i) === BROKEN } // it must be broken in this case, no other way around it diff --git a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt index c5d0ccf187..4f428bc4b0 100644 --- a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt +++ b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt @@ -4,6 +4,7 @@ package kotlinx.coroutines.sync +import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlin.test.* @@ -106,4 +107,4 @@ class MutexTest : TestBase() { assertFalse(mutex.holdsLock(firstOwner)) assertFalse(mutex.holdsLock(secondOwner)) } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt b/kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt index bb713b258d..027f3c514d 100644 --- a/kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/sync/MutexStressTest.kt @@ -90,4 +90,21 @@ class MutexStressTest : TestBase() { } } } -} \ No newline at end of file + + @Test + fun testShouldBeUnlockedOnCancellation() = runTest { + val mutex = Mutex() + val n = 1000 * stressTestMultiplier + repeat(n) { + val job = launch(Dispatchers.Default) { + mutex.lock() + mutex.unlock() + } + mutex.withLock { + job.cancel() + } + job.join() + assertFalse { mutex.isLocked } + } + } +} diff --git a/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt b/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt index 374a1e3d7c..2ceed64b95 100644 --- a/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/sync/SemaphoreStressTest.kt @@ -2,7 +2,7 @@ package kotlinx.coroutines.sync import kotlinx.coroutines.* import org.junit.Test -import kotlin.test.assertEquals +import kotlin.test.* class SemaphoreStressTest : TestBase() { @Test @@ -90,4 +90,21 @@ class SemaphoreStressTest : TestBase() { } } } + + @Test + fun testShouldBeUnlockedOnCancellation() = runTest { + val semaphore = Semaphore(1) + val n = 1000 * stressTestMultiplier + repeat(n) { + val job = launch(Dispatchers.Default) { + semaphore.acquire() + semaphore.release() + } + semaphore.withPermit { + job.cancel() + } + job.join() + assertTrue { semaphore.availablePermits == 1 } + } + } } From 31a8df0257e584f979f89832fdf90a464626b2fd Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Wed, 18 Nov 2020 12:25:41 +0300 Subject: [PATCH 09/15] Clarify thread-safety of SharedFlow methods in docs (#2399) * Clarify thread-safety of SharedFlow methods in docs * Override MutableSharedFlow.emit to attach a more appropriate docs than the one inherited from FlowCollector. * Clarify thread-safety of all the MutableSharedFlow & MutableState "mutating" methods. The latter is needed, because Flows, in general, are sequential, but shared flows provide all the necessarily synchronization themselves, so, to avoid confusion it makes sense to additionally mention thread-safety of shared flows in all the relevant mutating functions. --- .../api/kotlinx-coroutines-core.api | 1 + .../common/src/flow/SharedFlow.kt | 17 +++++++++++++++++ .../common/src/flow/StateFlow.kt | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index b86076fca1..dcd837f7b2 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -1064,6 +1064,7 @@ public final class kotlinx/coroutines/flow/LintKt { } public abstract interface class kotlinx/coroutines/flow/MutableSharedFlow : kotlinx/coroutines/flow/FlowCollector, kotlinx/coroutines/flow/SharedFlow { + public abstract fun emit (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getSubscriptionCount ()Lkotlinx/coroutines/flow/StateFlow; public abstract fun resetReplayCache ()V public abstract fun tryEmit (Ljava/lang/Object;)Z diff --git a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt index a3075b927a..75f9e710f7 100644 --- a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt +++ b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt @@ -147,6 +147,17 @@ public interface SharedFlow : Flow { * Use the `MutableSharedFlow(...)` constructor function to create an implementation. */ public interface MutableSharedFlow : SharedFlow, FlowCollector { + /** + * Emits a [value] to this shared flow, suspending on buffer overflow if the shared flow was created + * with the default [BufferOverflow.SUSPEND] strategy. + * + * See [tryEmit] for a non-suspending variant of this function. + * + * This method is **thread-safe** and can be safely invoked from concurrent coroutines without + * external synchronization. + */ + override suspend fun emit(value: T) + /** * Tries to emit a [value] to this shared flow without suspending. It returns `true` if the value was * emitted successfully. When this function returns `false`, it means that the call to a plain [emit] @@ -155,6 +166,9 @@ public interface MutableSharedFlow : SharedFlow, FlowCollector { * A shared flow configured with a [BufferOverflow] strategy other than [SUSPEND][BufferOverflow.SUSPEND] * (either [DROP_OLDEST][BufferOverflow.DROP_OLDEST] or [DROP_LATEST][BufferOverflow.DROP_LATEST]) never * suspends on [emit], and thus `tryEmit` to such a shared flow always returns `true`. + * + * This method is **thread-safe** and can be safely invoked from concurrent coroutines without + * external synchronization. */ public fun tryEmit(value: T): Boolean @@ -190,6 +204,9 @@ public interface MutableSharedFlow : SharedFlow, FlowCollector { * supported, and throws an [UnsupportedOperationException]. To reset a [MutableStateFlow] * to an initial value, just update its [value][MutableStateFlow.value]. * + * This method is **thread-safe** and can be safely invoked from concurrent coroutines without + * external synchronization. + * * **Note: This is an experimental api.** This function may be removed or renamed in the future. */ @ExperimentalCoroutinesApi diff --git a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt index a9a4ed3d24..45641ca92d 100644 --- a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt +++ b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt @@ -160,6 +160,9 @@ public interface MutableStateFlow : StateFlow, MutableSharedFlow { * The current value of this state flow. * * Setting a value that is [equal][Any.equals] to the previous one does nothing. + * + * This property is **thread-safe** and can be safely updated from concurrent coroutines without + * external synchronization. */ public override var value: T @@ -170,6 +173,9 @@ public interface MutableStateFlow : StateFlow, MutableSharedFlow { * This function use a regular comparison using [Any.equals]. If both [expect] and [update] are equal to the * current [value], this function returns `true`, but it does not actually change the reference that is * stored in the [value]. + * + * This method is **thread-safe** and can be safely invoked from concurrent coroutines without + * external synchronization. */ public fun compareAndSet(expect: T, update: T): Boolean } From 81577b2af135b4509fdec0737022badce6cc8aeb Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Wed, 18 Nov 2020 12:26:13 +0300 Subject: [PATCH 10/15] Clarify withContext(NonCancellable) behavior (#2400) Fixes #2383 --- kotlinx-coroutines-core/common/src/Builders.common.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kotlinx-coroutines-core/common/src/Builders.common.kt b/kotlinx-coroutines-core/common/src/Builders.common.kt index b7deaccb72..6ef1a8daea 100644 --- a/kotlinx-coroutines-core/common/src/Builders.common.kt +++ b/kotlinx-coroutines-core/common/src/Builders.common.kt @@ -133,6 +133,10 @@ private class LazyDeferredCoroutine( * which means that if the original [coroutineContext], in which `withContext` was invoked, * is cancelled by the time its dispatcher starts to execute the code, * it discards the result of `withContext` and throws [CancellationException]. + * + * The cancellation behaviour described above is enabled if and only if the dispatcher is being changed. + * For example, when using `withContext(NonCancellable) { ... }` there is no change in dispatcher and + * this call will not be cancelled neither on entry to the block inside `withContext` nor on exit from it. */ public suspend fun withContext( context: CoroutineContext, From 179f14219d66b63841bb5ab563ab3fba5fc11a05 Mon Sep 17 00:00:00 2001 From: Alexander Likhachev Date: Wed, 18 Nov 2020 17:54:33 +0300 Subject: [PATCH 11/15] Add build parameter to build coroutines with JVM IR compiler (#2389) * Add build parameters to enable JVM IR and disable native targets * enable_jvm_ir enables JVM IR compiler * disable_native_targets disables native targets in train builds * enable_jvm_ir_api_check enables JVM IR API check (works only if enable_jvm_ir is set) * Fix "Return type must be specified in explicit API mode" in 1.4.20 --- build.gradle | 13 +++++++++++ gradle/compile-jvm-multiplatform.gradle | 8 +++++-- gradle/compile-jvm.gradle | 6 +++++ kotlinx-coroutines-core/build.gradle | 22 +++++++++++++++---- .../common/src/internal/DispatchedTask.kt | 2 +- .../common/src/selects/SelectUnbiased.kt | 2 +- .../jvm/src/internal/LockFreeLinkedList.kt | 6 ++--- kotlinx-coroutines-debug/build.gradle | 10 +++++++++ 8 files changed, 58 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index 79c7f3553e..938d42e7a1 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,10 @@ buildscript { throw new IllegalArgumentException("'kotlin_snapshot_version' should be defined when building with snapshot compiler") } } + // These three flags are enabled in train builds for JVM IR compiler testing + ext.jvm_ir_enabled = rootProject.properties['enable_jvm_ir'] != null + ext.jvm_ir_api_check_enabled = rootProject.properties['enable_jvm_ir_api_check'] != null + ext.native_targets_enabled = rootProject.properties['disable_native_targets'] == null // Determine if any project dependency is using a snapshot version ext.using_snapshot_version = build_snapshot_train @@ -323,3 +327,12 @@ knit { } knitPrepare.dependsOn getTasksByName("dokka", true) + +// Disable binary compatibility check for JVM IR compiler output by default +if (jvm_ir_enabled) { + subprojects { project -> + configure(tasks.matching { it.name == "apiCheck" }) { + enabled = enabled && jvm_ir_api_check_enabled + } + } +} \ No newline at end of file diff --git a/gradle/compile-jvm-multiplatform.gradle b/gradle/compile-jvm-multiplatform.gradle index e72d30511e..44b0cbedba 100644 --- a/gradle/compile-jvm-multiplatform.gradle +++ b/gradle/compile-jvm-multiplatform.gradle @@ -6,8 +6,12 @@ sourceCompatibility = 1.6 targetCompatibility = 1.6 kotlin { - targets { - fromPreset(presets.jvm, 'jvm') + jvm { + if (rootProject.ext.jvm_ir_enabled) { + compilations.all { + kotlinOptions.useIR = true + } + } } sourceSets { jvmTest.dependencies { diff --git a/gradle/compile-jvm.gradle b/gradle/compile-jvm.gradle index caa5c45f60..bd2ae14775 100644 --- a/gradle/compile-jvm.gradle +++ b/gradle/compile-jvm.gradle @@ -9,6 +9,12 @@ apply plugin: 'org.jetbrains.kotlin.jvm' sourceCompatibility = 1.6 targetCompatibility = 1.6 +if (rootProject.ext.jvm_ir_enabled) { + kotlin.target.compilations.all { + kotlinOptions.useIR = true + } +} + dependencies { testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" // Workaround to make addSuppressed work in tests diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index f98f6a529c..314eea350b 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -5,8 +5,12 @@ apply plugin: 'org.jetbrains.kotlin.multiplatform' apply from: rootProject.file("gradle/compile-jvm-multiplatform.gradle") apply from: rootProject.file("gradle/compile-common.gradle") + +if (rootProject.ext.native_targets_enabled) { + apply from: rootProject.file("gradle/compile-native-multiplatform.gradle") +} + apply from: rootProject.file("gradle/compile-js-multiplatform.gradle") -apply from: rootProject.file("gradle/compile-native-multiplatform.gradle") apply from: rootProject.file('gradle/publish-npm-js.gradle') /* ========================================================================== @@ -52,8 +56,11 @@ static boolean isNativeDarwin(String name) { return ["ios", "macos", "tvos", "wa static boolean isNativeOther(String name) { return ["linux", "mingw"].any { name.startsWith(it) } } defineSourceSet("concurrent", ["common"]) { it in ["jvm", "native"] } -defineSourceSet("nativeDarwin", ["native"]) { isNativeDarwin(it) } -defineSourceSet("nativeOther", ["native"]) { isNativeOther(it) } + +if (rootProject.ext.native_targets_enabled) { + defineSourceSet("nativeDarwin", ["native"]) { isNativeDarwin(it) } + defineSourceSet("nativeOther", ["native"]) { isNativeOther(it) } +} /* ========================================================================== */ @@ -129,7 +136,7 @@ def configureNativeSourceSetPreset(name, preset) { } // :KLUDGE: Idea.active: Configure platform libraries for native source sets when working in IDEA -if (Idea.active) { +if (Idea.active && rootProject.ext.native_targets_enabled) { def manager = project.ext.hostManager def linuxPreset = kotlin.presets.linuxX64 def macosPreset = kotlin.presets.macosX64 @@ -183,6 +190,13 @@ jvmTest { exclude '**/*StressTest.*' } systemProperty 'kotlinx.coroutines.scheduler.keep.alive.sec', '100000' // any unpark problem hangs test + + // TODO: JVM IR generates different stacktrace so temporary disable stacktrace tests + if (rootProject.ext.jvm_ir_enabled) { + filter { + excludeTestsMatching('kotlinx.coroutines.exceptions.StackTraceRecovery*') + } + } } jvmJar { diff --git a/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt b/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt index 1f4942a358..caf87f143e 100644 --- a/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt +++ b/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt @@ -23,7 +23,7 @@ internal const val MODE_ATOMIC = 0 * **DO NOT CHANGE THE CONSTANT VALUE**. It is being into the user code from [suspendCancellableCoroutine]. */ @PublishedApi -internal const val MODE_CANCELLABLE = 1 +internal const val MODE_CANCELLABLE: Int = 1 /** * Cancellable dispatch mode for [suspendCancellableCoroutineReusable]. diff --git a/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt index edcf123b0a..d691c725b5 100644 --- a/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt +++ b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt @@ -36,7 +36,7 @@ internal class UnbiasedSelectBuilderImpl(uCont: Continuation) : val clauses = arrayListOf<() -> Unit>() @PublishedApi - internal fun handleBuilderException(e: Throwable) = instance.handleBuilderException(e) + internal fun handleBuilderException(e: Throwable): Unit = instance.handleBuilderException(e) @PublishedApi internal fun initSelectResult(): Any? { diff --git a/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt index 97f9978139..d08f41bf8a 100644 --- a/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/LockFreeLinkedList.kt @@ -11,13 +11,13 @@ import kotlinx.coroutines.* private typealias Node = LockFreeLinkedListNode @PublishedApi -internal const val UNDECIDED = 0 +internal const val UNDECIDED: Int = 0 @PublishedApi -internal const val SUCCESS = 1 +internal const val SUCCESS: Int = 1 @PublishedApi -internal const val FAILURE = 2 +internal const val FAILURE: Int = 2 @PublishedApi internal val CONDITION_FALSE: Any = Symbol("CONDITION_FALSE") diff --git a/kotlinx-coroutines-debug/build.gradle b/kotlinx-coroutines-debug/build.gradle index ab7f28c6a8..2a11bbb38c 100644 --- a/kotlinx-coroutines-debug/build.gradle +++ b/kotlinx-coroutines-debug/build.gradle @@ -28,6 +28,16 @@ dependencies { api "net.java.dev.jna:jna-platform:$jna_version" } +// TODO: JVM IR generates different stacktrace so temporary disable stacktrace tests +if (rootProject.ext.jvm_ir_enabled) { + tasks.named('test', Test) { + filter { +// excludeTest('kotlinx.coroutines.debug.CoroutinesDumpTest', 'testCreationStackTrace') + excludeTestsMatching('kotlinx.coroutines.debug.DebugProbesTest') + } + } +} + jar { manifest { attributes "Premain-Class": "kotlinx.coroutines.debug.AgentPremain" From c542b83a07667f50f74d6b8d23975b63a54446f3 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 25 Nov 2020 19:33:31 +0300 Subject: [PATCH 12/15] Fix indents in the documentation --- kotlinx-coroutines-debug/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md index 5525f9129f..faf55ba61a 100644 --- a/kotlinx-coroutines-debug/README.md +++ b/kotlinx-coroutines-debug/README.md @@ -138,8 +138,8 @@ Coroutine "coroutine#2":DeferredCoroutine{Active}@289d1c02, state: SUSPENDED Dumping only deferred "coroutine#2":DeferredCoroutine{Active}, continuation is SUSPENDED at line kotlinx.coroutines.DeferredCoroutine.await$suspendImpl(Builders.common.kt:99) - "coroutine#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ExampleKt.computeOne(Example.kt:14) - "coroutine#4":DeferredCoroutine{Active}, continuation is SUSPENDED at line ExampleKt.computeTwo(Example.kt:19) + "coroutine#3":DeferredCoroutine{Active}, continuation is SUSPENDED at line ExampleKt.computeOne(Example.kt:14) + "coroutine#4":DeferredCoroutine{Active}, continuation is SUSPENDED at line ExampleKt.computeTwo(Example.kt:19) ``` ### Status of the API From 0e5aefdbe0cb565bad289cee4ff550fbe9e36e8c Mon Sep 17 00:00:00 2001 From: Roman Elizarov Date: Thu, 26 Nov 2020 13:46:54 +0300 Subject: [PATCH 13/15] Remove platform-specific dependencies on JavaFx artifacts (#2369) * Remove platform-specific dependencies on JavaFx artifacts Fixes #2360 --- ui/kotlinx-coroutines-javafx/build.gradle.kts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ui/kotlinx-coroutines-javafx/build.gradle.kts b/ui/kotlinx-coroutines-javafx/build.gradle.kts index 112441e0ed..e850e3940e 100644 --- a/ui/kotlinx-coroutines-javafx/build.gradle.kts +++ b/ui/kotlinx-coroutines-javafx/build.gradle.kts @@ -3,13 +3,20 @@ */ plugins { - id("org.openjfx.javafxplugin") + id("org.openjfx.javafxplugin") version "0.0.9" } javafx { version = version("javafx") modules = listOf("javafx.controls") - configuration = "compile" + configuration = "compileOnly" +} + +sourceSets { + test.configure { + compileClasspath += configurations.compileOnly + runtimeClasspath += configurations.compileOnly + } } val JDK_18: String? by lazy { From c742db28835f82c50a55971cf25ba621795cff34 Mon Sep 17 00:00:00 2001 From: Vadim Semenov <6957841+vadimsemenov@users.noreply.github.com> Date: Thu, 26 Nov 2020 10:52:41 +0000 Subject: [PATCH 14/15] Repair some corner cases in cancellation propagation between coroutines and listenable futures (#2222) * Repair some corner cases in cancellation propagation between coroutines and listenable futures Implement bidirectional cancellation for `future` coroutine builder. This also: * Refactors JobListenableFuture infrastructure so it can be reused in CoroutineScope.future and Deferred.asListenableFuture; * Provides more descriptive `toString` implementation for the returned Future; * Fixes stack traces in thrown exception, so it includes a call to get() that triggered the exception to be thrown; * Hides ListenableFuture.asDeferred return type, so it can't be cast to CompletableDeferred; * Adds more tests to cover fixed corner cases; * Improves documentation; * Suppresses annoying warnings in tests. Fixes #1442 --- .../src/ListenableFuture.kt | 290 ++++++++++-------- .../test/ListenableFutureTest.kt | 163 +++++++++- 2 files changed, 307 insertions(+), 146 deletions(-) diff --git a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt index 974e246283..6d1fab3d69 100644 --- a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt +++ b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt @@ -17,8 +17,11 @@ import kotlin.coroutines.* * The coroutine is immediately started. Passing [CoroutineStart.LAZY] to [start] throws * [IllegalArgumentException], because Futures don't have a way to start lazily. * - * The created coroutine is cancelled when the resulting future completes successfully, fails, or - * is cancelled. + * When the created coroutine [isCompleted][Job.isCompleted], it will try to + * *synchronously* complete the returned Future with the same outcome. This will + * succeed, barring a race with external cancellation of returned [ListenableFuture]. + * + * Cancellation is propagated bidirectionally. * * `CoroutineContext` is inherited from this [CoroutineScope]. Additional context elements can be * added/overlaid by passing [context]. @@ -32,8 +35,10 @@ import kotlin.coroutines.* * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging * facilities. * - * Note that the error and cancellation semantics of [future] are _subtly different_ than - * [asListenableFuture]'s. See [ListenableFutureCoroutine] for details. + * Note that the error and cancellation semantics of [future] are _subtly different_ than [asListenableFuture]'s. + * In particular, any exception that happens in the coroutine after returned future is + * successfully cancelled will be passed to the [CoroutineExceptionHandler] from the [context]. + * See [ListenableFutureCoroutine] for details. * * @param context added overlaying [CoroutineScope.coroutineContext] to form the new context. * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. @@ -46,14 +51,9 @@ public fun CoroutineScope.future( ): ListenableFuture { require(!start.isLazy) { "$start start is not supported" } val newContext = newCoroutineContext(context) - val future = SettableFuture.create() - val coroutine = ListenableFutureCoroutine(newContext, future) - future.addListener( - coroutine, - MoreExecutors.directExecutor()) + val coroutine = ListenableFutureCoroutine(newContext) coroutine.start(start, coroutine, block) - // Return hides the SettableFuture. This should prevent casting. - return object: ListenableFuture by future {} + return coroutine.future } /** @@ -70,7 +70,7 @@ public fun CoroutineScope.future( * When `this` `ListenableFuture` is [successfully cancelled][java.util.concurrent.Future.cancel], * it will cancel the returned `Deferred`. * - * When the returned `Deferred` is [cancelled][Deferred.cancel()], it will try to propagate the + * When the returned `Deferred` is [cancelled][Deferred.cancel], it will try to propagate the * cancellation to `this` `ListenableFuture`. Propagation will succeed, barring a race with the * `ListenableFuture` completing normally. This is the only case in which the returned `Deferred` * will complete with a different outcome than `this` `ListenableFuture`. @@ -152,7 +152,8 @@ public fun ListenableFuture.asDeferred(): Deferred { deferred.invokeOnCompletion { cancel(false) } - return deferred + // Return hides the CompletableDeferred. This should prevent casting. + return object : Deferred by deferred {} } /** @@ -166,7 +167,7 @@ public fun ListenableFuture.asDeferred(): Deferred { * state - a serious fundamental bug. */ private fun ExecutionException.nonNullCause(): Throwable { - return this.cause!! + return this.cause!! } /** @@ -195,13 +196,21 @@ private fun ExecutionException.nonNullCause(): Throwable { * * This is inherently a race. See [Future.cancel] for a description of `Future` cancellation * semantics. See [Job] for a description of coroutine cancellation semantics. See - * [DeferredListenableFuture.cancel] for greater detail on the overlapped cancellation semantics and + * [JobListenableFuture.cancel] for greater detail on the overlapped cancellation semantics and * corner cases of this method. */ public fun Deferred.asListenableFuture(): ListenableFuture { - val outerFuture = OuterFuture(this) - outerFuture.afterInit() - return outerFuture + val listenableFuture = JobListenableFuture(this) + // This invokeOnCompletion completes the JobListenableFuture with the same result as `this` Deferred. + // The JobListenableFuture may have completed earlier if it got cancelled! See JobListenableFuture.cancel(). + invokeOnCompletion { throwable -> + if (throwable == null) { + listenableFuture.complete(getCompleted()) + } else { + listenableFuture.completeExceptionallyOrCancel(throwable) + } + } + return listenableFuture } /** @@ -215,7 +224,6 @@ public fun Deferred.asListenableFuture(): ListenableFuture { * This method is intended to be used with one-shot Futures, so on coroutine cancellation, the Future is cancelled as well. * If cancelling the given future is undesired, use [Futures.nonCancellationPropagating] or * [kotlinx.coroutines.NonCancellable]. - * */ public suspend fun ListenableFuture.await(): T { try { @@ -255,8 +263,7 @@ private class ToContinuation( continuation.cancel() } else { try { - continuation.resumeWith( - Result.success(Uninterruptibles.getUninterruptibly(futureToObserve))) + continuation.resume(Uninterruptibles.getUninterruptibly(futureToObserve)) } catch (e: ExecutionException) { // ExecutionException is the only kind of exception that can be thrown from a gotten // Future. Anything else showing up here indicates a very fundamental bug in a @@ -271,57 +278,46 @@ private class ToContinuation( * An [AbstractCoroutine] intended for use directly creating a [ListenableFuture] handle to * completion. * - * The code in the [Runnable] portion of the class is registered as a [ListenableFuture] callback. - * See [run] for details. Both types are implemented by this object to save an allocation. + * If [future] is successfully cancelled, cancellation is propagated to `this` `Coroutine`. + * By documented contract, a [Future] has been cancelled if + * and only if its `isCancelled()` method returns true. + * + * Any error that occurs after successfully cancelling a [ListenableFuture] will be passed + * to the [CoroutineExceptionHandler] from the context. The contract of [Future] does not permit + * it to return an error after it is successfully cancelled. + * + * By calling [asListenableFuture] on a [Deferred], any error that occurs after successfully + * cancelling the [ListenableFuture] representation of the [Deferred] will _not_ be passed to + * the [CoroutineExceptionHandler]. Cancelling a [Deferred] places that [Deferred] in the + * cancelling/cancelled states defined by [Job], which _can_ show the error. It's assumed that + * the [Deferred] pointing to the task will be used to observe any error outcome occurring after + * cancellation. + * + * This may be counterintuitive, but it maintains the error and cancellation contracts of both + * the [Deferred] and [ListenableFuture] types, while permitting both kinds of promise to point + * to the same running task. */ private class ListenableFutureCoroutine( - context: CoroutineContext, - private val future: SettableFuture -) : AbstractCoroutine(context), Runnable { + context: CoroutineContext +) : AbstractCoroutine(context) { - /** - * When registered as a [ListenableFuture] listener, cancels the returned [Coroutine] if - * [future] is successfully cancelled. By documented contract, a [Future] has been cancelled if - * and only if its `isCancelled()` method returns true. - * - * Any error that occurs after successfully cancelling a [ListenableFuture] - * created by submitting the returned object as a [Runnable] to an `Executor` will be passed - * to the [CoroutineExceptionHandler] from the context. The contract of [Future] does not permit - * it to return an error after it is successfully cancelled. - * - * By calling [asListenableFuture] on a [Deferred], any error that occurs after successfully - * cancelling the [ListenableFuture] representation of the [Deferred] will _not_ be passed to - * the [CoroutineExceptionHandler]. Cancelling a [Deferred] places that [Deferred] in the - * cancelling/cancelled states defined by [Job], which _can_ show the error. It's assumed that - * the [Deferred] pointing to the task will be used to observe any error outcome occurring after - * cancellation. - * - * This may be counterintuitive, but it maintains the error and cancellation contracts of both - * the [Deferred] and [ListenableFuture] types, while permitting both kinds of promise to point - * to the same running task. - */ - override fun run() { - if (future.isCancelled) { - cancel() - } - } + // JobListenableFuture propagates external cancellation to `this` coroutine. See JobListenableFuture. + @JvmField val future = JobListenableFuture(this) override fun onCompleted(value: T) { - future.set(value) + future.complete(value) } - // TODO: This doesn't actually cancel the Future. There doesn't seem to be bidi cancellation? override fun onCancelled(cause: Throwable, handled: Boolean) { - if (!future.setException(cause) && !handled) { - // prevents loss of exception that was not handled by parent & could not be set to SettableFuture + if (!future.completeExceptionallyOrCancel(cause) && !handled) { + // prevents loss of exception that was not handled by parent & could not be set to JobListenableFuture handleCoroutineException(context, cause) } } } /** - * A [ListenableFuture] that delegates to an internal [DeferredListenableFuture], collaborating with - * it. + * A [ListenableFuture] that delegates to an internal [SettableFuture], collaborating with it. * * This setup allows the returned [ListenableFuture] to maintain the following properties: * @@ -333,130 +329,154 @@ private class ListenableFutureCoroutine( * - Fully correct cancellation and listener happens-after obeying [Future] and * [ListenableFuture]'s documented and implicit contracts is surprisingly difficult to achieve. * The best way to be correct, especially given the fun corner cases from - * [AsyncFuture.setAsync], is to just use an [AsyncFuture]. - * - To maintain sanity, this class implements [ListenableFuture] and uses an inner [AsyncFuture] - * around its input [deferred] as a state engine to establish happens-after-completion. This - * could probably be compressed into one subclass of [AsyncFuture] to save an allocation, at the + * [AbstractFuture.setFuture], is to just use an [AbstractFuture]. + * - To maintain sanity, this class implements [ListenableFuture] and uses an auxiliary [SettableFuture] + * around coroutine's result as a state engine to establish happens-after-completion. This + * could probably be compressed into one subclass of [AbstractFuture] to save an allocation, at the * cost of the implementation's readability. */ -private class OuterFuture(private val deferred: Deferred): ListenableFuture { - val innerFuture = DeferredListenableFuture(deferred) +private class JobListenableFuture(private val jobToCancel: Job): ListenableFuture { + /** + * Serves as a state machine for [Future] cancellation. + * + * [AbstractFuture] has a highly-correct atomic implementation of `Future`'s completion and + * cancellation semantics. By using that type, the [JobListenableFuture] can delegate its semantics to + * `auxFuture.get()` the result in such a way that the `Deferred` is always complete when returned. + * + * To preserve Coroutine's [CancellationException], this future points to either `T` or [Cancelled]. + */ + private val auxFuture = SettableFuture.create() - // Adding the listener after initialization resolves partial construction hairpin problem. - // - // This invokeOnCompletion completes the innerFuture as `deferred` does. The innerFuture may - // have completed earlier if it got cancelled! See DeferredListenableFuture. - fun afterInit() { - deferred.invokeOnCompletion { - innerFuture.complete() - } - } + /** + * When the attached coroutine [isCompleted][Job.isCompleted] successfully + * its outcome should be passed to this method. + * + * This should succeed barring a race with external cancellation. + */ + fun complete(result: T): Boolean = auxFuture.set(result) + + /** + * When the attached coroutine [isCompleted][Job.isCompleted] [exceptionally][Job.isCancelled] + * its outcome should be passed to this method. + * + * This method will map coroutine's exception into corresponding Future's exception. + * + * This should succeed barring a race with external cancellation. + */ + // CancellationException is wrapped into `Cancelled` to preserve original cause and message. + // All the other exceptions are delegated to SettableFuture.setException. + fun completeExceptionallyOrCancel(t: Throwable): Boolean = + if (t is CancellationException) auxFuture.set(Cancelled(t)) else auxFuture.setException(t) /** * Returns cancellation _in the sense of [Future]_. This is _not_ equivalent to * [Job.isCancelled]. * - * When done, this Future is cancelled if its innerFuture is cancelled, or if its delegate - * [deferred] is cancelled. Cancellation of [innerFuture] collaborates with this class. + * When done, this Future is cancelled if its [auxFuture] is cancelled, or if [auxFuture] + * contains [CancellationException]. * - * See [DeferredListenableFuture.cancel]. + * See [cancel]. */ override fun isCancelled(): Boolean { // This expression ensures that isCancelled() will *never* return true when isDone() returns false. // In the case that the deferred has completed with cancellation, completing `this`, its // reaching the "cancelled" state with a cause of CancellationException is treated as the - // same thing as innerFuture getting cancelled. If the Job is in the "cancelling" state and + // same thing as auxFuture getting cancelled. If the Job is in the "cancelling" state and // this Future hasn't itself been successfully cancelled, the Future will return // isCancelled() == false. This is the only discovered way to reconcile the two different // cancellation contracts. - return isDone - && (innerFuture.isCancelled - || deferred.getCompletionExceptionOrNull() is kotlinx.coroutines.CancellationException) + return auxFuture.isCancelled || (isDone && Uninterruptibles.getUninterruptibly(auxFuture) is Cancelled) } /** - * Waits for [innerFuture] to complete by blocking, then uses the [deferred] returned by that - * Future to get the `T` value `this` [ListenableFuture] is pointing to. This establishes - * happens-after ordering for completion of the [Deferred] input to [OuterFuture]. + * Waits for [auxFuture] to complete by blocking, then uses its `result` + * to get the `T` value `this` [ListenableFuture] is pointing to or throw a [CancellationException]. + * This establishes happens-after ordering for completion of the entangled coroutine. * - * `innerFuture` _must be complete_ in order for the [isDone] and [isCancelled] happens-after - * contract of [Future] to be correctly followed. If this method were to directly use - * _`this.deferred`_ instead of blocking on its `innerFuture`, the [Deferred] that this - * [ListenableFuture] is created from might be in an incomplete state when used by `get()`. + * [SettableFuture.get] can only throw [CancellationException] if it was cancelled externally. + * Otherwise it returns [Cancelled] that encapsulates outcome of the entangled coroutine. + * + * [auxFuture] _must be complete_ in order for the [isDone] and [isCancelled] happens-after + * contract of [Future] to be correctly followed. */ override fun get(): T { - return getInternal(innerFuture.get()) + return getInternal(auxFuture.get()) } /** See [get()]. */ override fun get(timeout: Long, unit: TimeUnit): T { - return getInternal(innerFuture.get(timeout, unit)) + return getInternal(auxFuture.get(timeout, unit)) } /** See [get()]. */ - private fun getInternal(deferred: Deferred): T { - if (deferred.isCancelled) { - val exception = deferred.getCompletionExceptionOrNull() - if (exception is kotlinx.coroutines.CancellationException) { - throw exception - } else { - throw ExecutionException(exception) - } - } else { - return deferred.getCompleted() - } + private fun getInternal(result: Any): T = if (result is Cancelled) { + throw CancellationException().initCause(result.exception) + } else { + // We know that `auxFuture` can contain either `T` or `Cancelled`. + @Suppress("UNCHECKED_CAST") + result as T } override fun addListener(listener: Runnable, executor: Executor) { - innerFuture.addListener(listener, executor) + auxFuture.addListener(listener, executor) } override fun isDone(): Boolean { - return innerFuture.isDone - } - - override fun cancel(mayInterruptIfRunning: Boolean): Boolean { - return innerFuture.cancel(mayInterruptIfRunning) - } -} - -/** - * Holds a delegate deferred, and serves as a state machine for [Future] cancellation. - * - * [AbstractFuture] has a highly-correct atomic implementation of `Future`'s completion and - * cancellation semantics. By using that type, the [OuterFuture] can delegate its semantics to - * _this_ `Future` `get()` the result in such a way that the `Deferred` is always complete when - * returned. - */ -private class DeferredListenableFuture( - private val deferred: Deferred -) : AbstractFuture>() { - - fun complete() { - set(deferred) + return auxFuture.isDone } /** - * Tries to cancel the task. This is fundamentally racy. + * Tries to cancel [jobToCancel] if `this` future was cancelled. This is fundamentally racy. * - * For any given call to `cancel()`, if [deferred] is already completed, the call will complete - * this Future with it, and fail to cancel. Otherwise, the - * call to `cancel()` will try to cancel this Future: if and only if cancellation of this - * succeeds, [deferred] will have its [Deferred.cancel] called. + * The call to `cancel()` will try to cancel [auxFuture]: if and only if cancellation of [auxFuture] + * succeeds, [jobToCancel] will have its [Job.cancel] called. * - * This arrangement means that [deferred] _might not successfully cancel_, if the race resolves - * in a particular way. [deferred] may also be in its "cancelling" state while this + * This arrangement means that [jobToCancel] _might not successfully cancel_, if the race resolves + * in a particular way. [jobToCancel] may also be in its "cancelling" state while this * ListenableFuture is complete and cancelled. - * - * [OuterFuture] collaborates with this class to present a more cohesive picture and ensure - * that certain combinations of cancelled/cancelling states can't be observed. */ override fun cancel(mayInterruptIfRunning: Boolean): Boolean { - return if (super.cancel(mayInterruptIfRunning)) { - deferred.cancel() + // TODO: call jobToCancel.cancel() _before_ running the listeners. + // `auxFuture.cancel()` will execute auxFuture's listeners. This delays cancellation of + // `jobToCancel` until after auxFuture's listeners have already run. + // Consider moving `jobToCancel.cancel()` into [AbstractFuture.afterDone] when the API is finalized. + return if (auxFuture.cancel(mayInterruptIfRunning)) { + jobToCancel.cancel() true } else { false } } + + override fun toString(): String = buildString { + append(super.toString()) + append("[status=") + if (isDone) { + try { + when (val result = Uninterruptibles.getUninterruptibly(auxFuture)) { + is Cancelled -> append("CANCELLED, cause=[${result.exception}]") + else -> append("SUCCESS, result=[$result") + } + } catch (e: CancellationException) { + // `this` future was cancelled by `Future.cancel`. In this case there's no cause or message. + append("CANCELLED") + } catch (e: ExecutionException) { + append("FAILURE, cause=[${e.cause}]") + } catch (t: Throwable) { + // Violation of Future's contract, should never happen. + append("UNKNOWN, cause=[${t.javaClass} thrown from get()]") + } + } else { + append("PENDING, delegate=[$auxFuture]") + } + } } + +/** + * A wrapper for `Coroutine`'s [CancellationException]. + * + * If the coroutine is _cancelled normally_, we want to show the reason of cancellation to the user. Unfortunately, + * [SettableFuture] can't store the reason of cancellation. To mitigate this, we wrap cancellation exception into this + * class and pass it into [SettableFuture.complete]. See implementation of [JobListenableFuture]. + */ +private class Cancelled(@JvmField val exception: CancellationException) diff --git a/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt b/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt index a9a7f7ba9d..dc2d99d7f7 100644 --- a/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt +++ b/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt @@ -7,6 +7,7 @@ package kotlinx.coroutines.guava import com.google.common.util.concurrent.* import kotlinx.coroutines.* import org.junit.* +import org.junit.Ignore import org.junit.Test import java.util.concurrent.* import java.util.concurrent.CancellationException @@ -315,6 +316,28 @@ class ListenableFutureTest : TestBase() { finish(4) } + @Test + @Ignore // TODO: propagate cancellation before running listeners. + fun testAsListenableFuturePropagatesCancellationBeforeRunningListeners() = runTest { + expect(1) + val deferred = async(context = Dispatchers.Unconfined) { + try { + delay(Long.MAX_VALUE) + } finally { + expect(3) // Cancelled. + } + } + val asFuture = deferred.asListenableFuture() + asFuture.addListener(Runnable { expect(4) }, MoreExecutors.directExecutor()) + assertFalse(asFuture.isDone) + expect(2) + asFuture.cancel(false) + assertTrue(asFuture.isDone) + assertTrue(asFuture.isCancelled) + assertFailsWith { deferred.await() } + finish(5) + } + @Test fun testFutureCancellation() = runTest { val future = awaitFutureWithCancel(true) @@ -333,15 +356,18 @@ class ListenableFutureTest : TestBase() { val outputCancellationException = assertFailsWith { asFuture.get() } - assertEquals(outputCancellationException.message, "Foobar") - assertTrue(outputCancellationException.cause is OutOfMemoryError) - assertEquals(outputCancellationException.cause?.message, "Foobaz") + val cause = outputCancellationException.cause + assertNotNull(cause) + assertEquals(cause.message, "Foobar") + assertTrue(cause.cause is OutOfMemoryError) + assertEquals(cause.cause?.message, "Foobaz") } @Test fun testNoFutureCancellation() = runTest { val future = awaitFutureWithCancel(false) assertFalse(future.isCancelled) + @Suppress("BlockingMethodInNonBlockingContext") assertEquals(42, future.get()) finish(4) } @@ -354,7 +380,7 @@ class ListenableFutureTest : TestBase() { assertTrue(asDeferredAsFuture.isCancelled) assertFailsWith { - val value: Int = asDeferredAsFuture.await() + asDeferredAsFuture.await() } } @@ -379,7 +405,7 @@ class ListenableFutureTest : TestBase() { assertTrue(asDeferred.isCancelled) assertFailsWith { - val value: Int = asDeferred.await() + asDeferred.await() } } @@ -433,7 +459,10 @@ class ListenableFutureTest : TestBase() { @Test fun testFutureCompletedWithNullFastPathAsDeferred() = runTest { val executor = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool()) - val future = executor.submit(Callable { null }).also { it.get() } + val future = executor.submit(Callable { null }).also { + @Suppress("BlockingMethodInNonBlockingContext") + it.get() + } assertNull(future.asDeferred().await()) } @@ -494,8 +523,10 @@ class ListenableFutureTest : TestBase() { val future = future(Dispatchers.Unconfined) { try { delay(Long.MAX_VALUE) - } finally { + expectUnreached() + } catch (e: CancellationException) { expect(2) + throw e } } @@ -507,17 +538,19 @@ class ListenableFutureTest : TestBase() { @Test fun testExceptionOnExternalCancellation() = runTest(expected = {it is TestException}) { - expect(1) val result = future(Dispatchers.Unconfined) { try { + expect(1) delay(Long.MAX_VALUE) - } finally { - expect(2) + expectUnreached() + } catch (e: CancellationException) { + expect(3) throw TestException() } } + expect(2) result.cancel(true) - finish(3) + finish(4) } @Test @@ -540,12 +573,120 @@ class ListenableFutureTest : TestBase() { finish(3) } + /** This test ensures that we never pass [CancellationException] to [CoroutineExceptionHandler]. */ + @Test + fun testCancellationExceptionOnExternalCancellation() = runTest { + expect(1) + // No parent here (NonCancellable), so nowhere to propagate exception + val result = future(NonCancellable + Dispatchers.Unconfined) { + try { + delay(Long.MAX_VALUE) + } finally { + expect(2) + throw TestCancellationException() // this exception cannot be handled + } + } + assertTrue(result.cancel(true)) + finish(3) + } + + @Test + fun testCancellingFutureContextJobCancelsFuture() = runTest { + expect(1) + val supervisorJob = SupervisorJob() + val future = future(context = supervisorJob) { + expect(2) + try { + delay(Long.MAX_VALUE) + expectUnreached() + } catch (e: CancellationException) { + expect(4) + throw e + } + } + yield() + expect(3) + supervisorJob.cancel(CancellationException("Parent cancelled", TestException())) + supervisorJob.join() + assertTrue(future.isDone) + assertTrue(future.isCancelled) + val thrown = assertFailsWith { future.get() } + val cause = thrown.cause + assertNotNull(cause) + assertTrue(cause is CancellationException) + assertEquals("Parent cancelled", cause.message) + assertTrue(cause.cause is TestException) + finish(5) + } + + @Test + fun testFutureChildException() = runTest { + val future = future(context = NonCancellable + Dispatchers.Unconfined) { + val foo = async { delay(Long.MAX_VALUE); 42 } + val bar = async { throw TestException() } + foo.await() + bar.await() + } + future.checkFutureException() + } + + @Test + fun testFutureIsDoneAfterChildrenCompleted() = runTest { + expect(1) + val testException = TestException() + // Don't propagate exception to the test and use different dispatchers as we are going to block test thread. + val future = future(context = NonCancellable + Dispatchers.Default) { + val foo = async { + try { + delay(Long.MAX_VALUE) + 42 + } finally { + withContext(NonCancellable) { + delay(200) + } + } + } + foo.invokeOnCompletion { + expect(3) + } + val bar = async { throw testException } + foo.await() + bar.await() + } + yield() + expect(2) + // Blocking get should succeed after internal coroutine completes. + val thrown = assertFailsWith { future.get() } + expect(4) + assertEquals(testException, thrown.cause) + finish(5) + } + + @Test + @Ignore // TODO: propagate cancellation before running listeners. + fun testFuturePropagatesCancellationBeforeRunningListeners() = runTest { + expect(1) + val future = future(context = Dispatchers.Unconfined) { + try { + delay(Long.MAX_VALUE) + } finally { + expect(3) // Cancelled. + } + } + future.addListener(Runnable { expect(4) }, MoreExecutors.directExecutor()) + assertFalse(future.isDone) + expect(2) + future.cancel(false) + assertTrue(future.isDone) + assertTrue(future.isCancelled) + finish(5) + } + private inline fun ListenableFuture<*>.checkFutureException() { val e = assertFailsWith { get() } val cause = e.cause!! assertTrue(cause is T) } + @Suppress("SuspendFunctionOnCoroutineScope") private suspend fun CoroutineScope.awaitFutureWithCancel(cancellable: Boolean): ListenableFuture { val latch = CountDownLatch(1) val executor = MoreExecutors.listeningDecorator(ForkJoinPool.commonPool()) From b221094cd8a2c417ab5878a2ab18ed96690eb031 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 26 Nov 2020 16:30:59 +0300 Subject: [PATCH 15/15] Version 1.4.2 --- CHANGES.md | 12 ++++++++++++ README.md | 16 ++++++++-------- gradle.properties | 2 +- kotlinx-coroutines-debug/README.md | 2 +- kotlinx-coroutines-test/README.md | 2 +- ui/coroutines-guide-ui.md | 2 +- .../animation-app/gradle.properties | 2 +- .../example-app/gradle.properties | 2 +- 8 files changed, 26 insertions(+), 14 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index baee6c4340..943280e796 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,17 @@ # Change log for kotlinx.coroutines +## Version 1.4.2 + +* Fixed `StackOverflowError` in `Job.toString` when `Job` is observed in its intermediate state (#2371). +* Improved liveness and latency of `Dispatchers.Default` and `Dispatchers.IO` in low-loaded mode (#2381). +* Improved performance of consecutive `Channel.cancel` invocations (#2384). +* `SharingStarted` is now `fun` interface (#2397). +* Additional lint settings for `SharedFlow` to catch programmatic errors early (#2376). +* Fixed bug when mutex and semaphore were not released during cancellation (#2390, thanks to @Tilps for reproducing). +* Some corner cases in cancellation propagation between coroutines and listenable futures are repaired (#1442, thanks to @vadimsemenov). +* Fixed unconditional cast to `CoroutineStackFrame` in exception recovery that triggered failures of instrumented code (#2386). +* Platform-specific dependencies are removed from `kotlinx-coroutines-javafx` (#2360). + ## Version 1.4.1 This is a patch release with an important fix to the `SharedFlow` implementation. diff --git a/README.md b/README.md index 7bd8e5a74b..77de32bbdc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![official JetBrains 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://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=1.4.1) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.4.1) +[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=1.4.2) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.4.2) [![Kotlin](https://img.shields.io/badge/kotlin-1.4.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/) @@ -86,7 +86,7 @@ Add dependencies (you can also add other modules that you need): org.jetbrains.kotlinx kotlinx-coroutines-core - 1.4.1 + 1.4.2 ``` @@ -104,7 +104,7 @@ Add dependencies (you can also add other modules that you need): ```groovy dependencies { - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2' } ``` @@ -130,7 +130,7 @@ Add dependencies (you can also add other modules that you need): ```groovy dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2") } ``` @@ -152,7 +152,7 @@ In common code that should get compiled for different platforms, you can add dep ```groovy commonMain { dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2") } } ``` @@ -163,7 +163,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.4.1' +implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2' ``` This gives you access to Android [Dispatchers.Main] @@ -190,7 +190,7 @@ packagingOptions { ### JS [Kotlin/JS](https://kotlinlang.org/docs/reference/js-overview.html) version of `kotlinx.coroutines` is published as -[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.4.1/jar) +[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.4.2/jar) (follow the link to get the dependency declaration snippet). You can also use [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotlinx-coroutines-core) package via NPM. @@ -198,7 +198,7 @@ You can also use [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotli ### Native [Kotlin/Native](https://kotlinlang.org/docs/reference/native-overview.html) version of `kotlinx.coroutines` is published as -[`kotlinx-coroutines-core-native`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-native/1.4.1/jar) +[`kotlinx-coroutines-core-native`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-native/1.4.2/jar) (follow the link to get the dependency declaration snippet). Only single-threaded code (JS-style) on Kotlin/Native is currently supported. diff --git a/gradle.properties b/gradle.properties index 1ffa02d1ae..9163cf5af1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ # # Kotlin -version=1.4.1-SNAPSHOT +version=1.4.2-SNAPSHOT group=org.jetbrains.kotlinx kotlin_version=1.4.0 diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md index faf55ba61a..fc9637a9d5 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.4.1.jar`. +You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.4.2.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 afcd4a3b3b..6022955254 100644 --- a/kotlinx-coroutines-test/README.md +++ b/kotlinx-coroutines-test/README.md @@ -9,7 +9,7 @@ This package provides testing utilities for effectively testing coroutines. Add `kotlinx-coroutines-test` to your project test dependencies: ``` dependencies { - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.1' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2' } ``` diff --git a/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md index 9c1251fe21..c2bbff22a8 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.4.1" +implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2" ``` 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 c4aa67585e..98898bc9dc 100644 --- a/ui/kotlinx-coroutines-android/animation-app/gradle.properties +++ b/ui/kotlinx-coroutines-android/animation-app/gradle.properties @@ -21,7 +21,7 @@ org.gradle.jvmargs=-Xmx1536m # org.gradle.parallel=true kotlin_version=1.4.0 -coroutines_version=1.4.1 +coroutines_version=1.4.2 android.useAndroidX=true android.enableJetifier=true diff --git a/ui/kotlinx-coroutines-android/example-app/gradle.properties b/ui/kotlinx-coroutines-android/example-app/gradle.properties index c4aa67585e..98898bc9dc 100644 --- a/ui/kotlinx-coroutines-android/example-app/gradle.properties +++ b/ui/kotlinx-coroutines-android/example-app/gradle.properties @@ -21,7 +21,7 @@ org.gradle.jvmargs=-Xmx1536m # org.gradle.parallel=true kotlin_version=1.4.0 -coroutines_version=1.4.1 +coroutines_version=1.4.2 android.useAndroidX=true android.enableJetifier=true