Merge branch 'master' into develop
diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt
index 139ef04..492e367 100644
--- a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt
+++ b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt
@@ -11,9 +11,9 @@
// --------------- cancellable continuations ---------------
/**
- * Cancellable continuation. It is _completed_ when it is resumed or cancelled.
- * When [cancel] function is explicitly invoked, this continuation immediately resumes with [CancellationException] or
- * with the specified cancel cause.
+ * Cancellable continuation. It is _completed_ when resumed or cancelled.
+ * When the [cancel] function is explicitly invoked, this continuation immediately resumes with a [CancellationException] or
+ * the specified cancel cause.
*
* Cancellable continuation has three states (as subset of [Job] states):
*
@@ -28,10 +28,10 @@
*
* A [cancelled][isCancelled] continuation implies that it is [completed][isCompleted].
*
- * Invocation of [resume] or [resumeWithException] in _resumed_ state produces [IllegalStateException].
+ * Invocation of [resume] or [resumeWithException] in _resumed_ state produces an [IllegalStateException].
* Invocation of [resume] in _cancelled_ state is ignored (it is a trivial race between resume from the continuation owner and
- * outer job cancellation and cancellation wins).
- * Invocation of [resumeWithException] in _cancelled_ state triggers exception handling of passed exception.
+ * outer job's cancellation, and the cancellation wins).
+ * Invocation of [resumeWithException] in _cancelled_ state triggers exception handling of the passed exception.
*
* ```
* +-----------+ resume +---------+
@@ -53,8 +53,8 @@
public val isActive: Boolean
/**
- * Returns `true` when this continuation has completed for any reason. A continuation
- * that was cancelled is also considered complete.
+ * Returns `true` when this continuation has completed for any reason. A cancelled continuation
+ * is also considered complete.
*/
public val isCompleted: Boolean
@@ -66,11 +66,11 @@
public val isCancelled: Boolean
/**
- * Tries to resume this continuation with a given value and returns non-null object token if it was successful,
- * or `null` otherwise (it was already resumed or cancelled). When non-null object was returned,
+ * Tries to resume this continuation with the specified [value] and returns a non-null object token if successful,
+ * or `null` otherwise (it was already resumed or cancelled). When a non-null object is returned,
* [completeResume] must be invoked with it.
*
- * When [idempotent] is not `null`, this function performs _idempotent_ operation, so that
+ * When [idempotent] is not `null`, this function performs an _idempotent_ operation, so that
* further invocations with the same non-null reference produce the same result.
*
* @suppress **This is unstable API and it is subject to change.**
@@ -79,8 +79,8 @@
public fun tryResume(value: T, idempotent: Any? = null): Any?
/**
- * Tries to resume this continuation with a given exception and returns non-null object token if it was successful,
- * or `null` otherwise (it was already resumed or cancelled). When non-null object was returned,
+ * Tries to resume this continuation with the specified [exception] and returns a non-null object token if successful,
+ * or `null` otherwise (it was already resumed or cancelled). When a non-null object is returned,
* [completeResume] must be invoked with it.
*
* @suppress **This is unstable API and it is subject to change.**
@@ -112,34 +112,34 @@
public fun initCancellability()
/**
- * Cancels this continuation with an optional cancellation [cause]. The result is `true` if this continuation was
- * cancelled as a result of this invocation and `false` otherwise.
+ * Cancels this continuation with an optional cancellation `cause`. The result is `true` if this continuation was
+ * cancelled as a result of this invocation, and `false` otherwise.
*/
public fun cancel(cause: Throwable? = null): Boolean
/**
- * Registers handler that is **synchronously** invoked once on cancellation (both regular and exceptional) of this continuation.
- * When the continuation is already cancelled, then the handler is immediately invoked
- * with cancellation exception. Otherwise, the handler will be invoked once on cancellation if this
+ * Registers a [handler] to be **synchronously** invoked on cancellation (regular or exceptional) of this continuation.
+ * When the continuation is already cancelled, the handler will be immediately invoked
+ * with the cancellation exception. Otherwise, the handler will be invoked as soon as this
* continuation is cancelled.
*
- * Installed [handler] should not throw any exceptions.
- * If it does, they will get caught, wrapped into [CompletionHandlerException] and
- * processed as uncaught exception in the context of the current coroutine
+ * The installed [handler] should not throw any exceptions.
+ * If it does, they will get caught, wrapped into a [CompletionHandlerException] and
+ * processed as an uncaught exception in the context of the current coroutine
* (see [CoroutineExceptionHandler]).
*
- * At most one [handler] can be installed on one continuation.
+ * At most one [handler] can be installed on a continuation.
*
* **Note**: Implementation of `CompletionHandler` must be fast, non-blocking, and thread-safe.
- * This handler can be invoked concurrently with the surrounding code.
- * There is no guarantee on the execution context in which the [handler] is invoked.
+ * This `handler` can be invoked concurrently with the surrounding code.
+ * There is no guarantee on the execution context in which the `handler` will be invoked.
*/
public fun invokeOnCancellation(handler: CompletionHandler)
/**
- * Resumes this continuation with a given [value] in the invoker thread without going though
- * [dispatch][CoroutineDispatcher.dispatch] function of the [CoroutineDispatcher] in the [context].
- * This function is designed to be used only by the [CoroutineDispatcher] implementations themselves.
+ * Resumes this continuation with the specified [value] in the invoker thread without going through
+ * the [dispatch][CoroutineDispatcher.dispatch] function of the [CoroutineDispatcher] in the [context].
+ * This function is designed to only be used by [CoroutineDispatcher] implementations.
* **It should not be used in general code**.
*
* **Note: This function is experimental.** Its signature general code may be changed in the future.
@@ -148,9 +148,9 @@
public fun CoroutineDispatcher.resumeUndispatched(value: T)
/**
- * Resumes this continuation with a given [exception] in the invoker thread without going though
- * [dispatch][CoroutineDispatcher.dispatch] function of the [CoroutineDispatcher] in the [context].
- * This function is designed to be used only by the [CoroutineDispatcher] implementations themselves.
+ * Resumes this continuation with the specified [exception] in the invoker thread without going through
+ * the [dispatch][CoroutineDispatcher.dispatch] function of the [CoroutineDispatcher] in the [context].
+ * This function is designed to only be used by [CoroutineDispatcher] implementations.
* **It should not be used in general code**.
*
* **Note: This function is experimental.** Its signature general code may be changed in the future.
@@ -159,19 +159,19 @@
public fun CoroutineDispatcher.resumeUndispatchedWithException(exception: Throwable)
/**
- * Resumes this continuation with a given [value] and calls the specified [onCancellation]
- * handler when resumed too late (when continuation was already cancelled) or when resumed
- * successfully (before cancellation), but coroutine's job was cancelled before it had a
- * chance to run in its dispatcher, so that suspended function threw an exception
+ * Resumes this continuation with the specified `value` and calls the specified `onCancellation`
+ * handler when either resumed too late (when continuation was already cancelled) or, although resumed
+ * successfully (before cancellation), the coroutine's job was cancelled before it had a
+ * chance to run in its dispatcher, so that the suspended function threw an exception
* instead of returning this value.
*
- * Installed [onCancellation] handler should not throw any exceptions.
- * If it does, they will get caught, wrapped into [CompletionHandlerException] and
- * processed as uncaught exception in the context of the current coroutine
+ * The installed [onCancellation] handler should not throw any exceptions.
+ * If it does, they will get caught, wrapped into a [CompletionHandlerException] and
+ * processed as an uncaught exception in the context of the current coroutine
* (see [CoroutineExceptionHandler]).
*
- * This function shall be used when resuming with a resource that must be closed by the
- * code that had called the corresponding suspending function, e.g.:
+ * This function shall be used when resuming with a resource that must be closed by
+ * code that called the corresponding suspending function, e.g.:
*
* ```
* continuation.resume(resource) {
@@ -179,17 +179,17 @@
* }
* ```
*
- * **Note**: Implementation of [onCancellation] handler must be fast, non-blocking, and thread-safe.
- * This handler can be invoked concurrently with the surrounding code.
- * There is no guarantee on the execution context in which the [onCancellation] handler is invoked.
+ * **Note**: The [onCancellation] handler must be fast, non-blocking, and thread-safe.
+ * It can be invoked concurrently with the surrounding code.
+ * There is no guarantee on the execution context of its invocation.
*/
@ExperimentalCoroutinesApi // since 1.2.0, tentatively graduates in 1.3.0
public fun resume(value: T, onCancellation: (cause: Throwable) -> Unit)
}
/**
- * Suspends coroutine similar to [suspendCoroutine], but provide an implementation of [CancellableContinuation] to
- * the [block]. This function throws [CancellationException] if the coroutine is cancelled or completed while suspended.
+ * Suspends the coroutine like [suspendCoroutine], but providing a [CancellableContinuation] to
+ * the [block]. This function throws a [CancellationException] if the coroutine is cancelled or completed while suspended.
*/
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit
@@ -204,9 +204,9 @@
}
/**
- * Suspends coroutine similar to [suspendCancellableCoroutine], but with *atomic cancellation*.
+ * Suspends the coroutine like [suspendCancellableCoroutine], but with *atomic cancellation*.
*
- * When suspended function throws [CancellationException] it means that the continuation was not resumed.
+ * When the suspended function throws a [CancellationException], it means that the continuation was not resumed.
* As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
* continue to execute even after it was cancelled from the same thread in the case when the continuation
* was already resumed and was posted for execution to the thread's queue.
@@ -238,15 +238,15 @@
suspendAtomicCancellableCoroutine(block)
/**
- * Removes a given node on cancellation.
+ * Removes the specified [node] on cancellation.
*/
internal fun CancellableContinuation<*>.removeOnCancellation(node: LockFreeLinkedListNode) =
invokeOnCancellation(handler = RemoveOnCancel(node).asHandler)
/**
- * Disposes a specified [handle] when this continuation is cancelled.
+ * Disposes the specified [handle] when this continuation is cancelled.
*
- * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created).
+ * This is a shortcut for the following code with slightly more efficient implementation (one fewer object created):
* ```
* invokeOnCancellation { handle.dispose() }
* ```
diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
index 785e8a7..a8b5686 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
@@ -7,7 +7,7 @@
import kotlin.coroutines.*
/**
- * Creates context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher nor
+ * Creates a context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher or
* [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on).
*/
public expect fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext
diff --git a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
index 19308c2..df7a2da 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
@@ -7,36 +7,36 @@
import kotlin.coroutines.*
/**
- * Base class that shall be extended by all coroutine dispatcher implementations.
+ * Base class to be extended by all coroutine dispatcher implementations.
*
* The following standard implementations are provided by `kotlinx.coroutines` as properties on
- * [Dispatchers] objects:
+ * the [Dispatchers] object:
*
- * * [Dispatchers.Default] -- is used by all standard builder if no dispatcher nor any other [ContinuationInterceptor]
+ * * [Dispatchers.Default] — is used by all standard builders if no dispatcher or any other [ContinuationInterceptor]
* is specified in their context. It uses a common pool of shared background threads.
* This is an appropriate choice for compute-intensive coroutines that consume CPU resources.
- * * [Dispatchers.IO] -- uses a shared pool of on-demand created threads and is designed for offloading of IO-intensive _blocking_
+ * * [Dispatchers.IO] — uses a shared pool of on-demand created threads and is designed for offloading of IO-intensive _blocking_
* operations (like file I/O and blocking socket I/O).
- * * [Dispatchers.Unconfined] -- starts coroutine execution in the current call-frame until the first suspension.
- * On first suspension the coroutine builder function returns.
- * The coroutine resumes in whatever thread that is used by the
+ * * [Dispatchers.Unconfined] — starts coroutine execution in the current call-frame until the first suspension,
+ * whereupon the coroutine builder function returns.
+ * The coroutine will later resume in whatever thread used by the
* corresponding suspending function, without confining it to any specific thread or pool.
- * **Unconfined dispatcher should not be normally used in code**.
+ * **The `Unconfined` dispatcher should not normally be used in code**.
* * Private thread pools can be created with [newSingleThreadContext] and [newFixedThreadPoolContext].
- * * An arbitrary [Executor][java.util.concurrent.Executor] can be converted to dispatcher with [asCoroutineDispatcher] extension function.
+ * * An arbitrary [Executor][java.util.concurrent.Executor] can be converted to a dispatcher with the [asCoroutineDispatcher] extension function.
*
* This class ensures that debugging facilities in [newCoroutineContext] function work properly.
*/
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
/**
- * Returns `true` if execution shall be dispatched onto another thread.
- * The default behaviour for most dispatchers is to return `true`.
+ * Returns `true` if the execution shall be dispatched onto another thread.
+ * The default behavior for most dispatchers is to return `true`.
*
* This method should never be used from general code, it is used only by `kotlinx.coroutines`
- * internals and its contract with the rest of API is an implementation detail.
+ * internals and its contract with the rest of the API is an implementation detail.
*
- * UI dispatchers _should not_ override `isDispatchNeeded`, but leave a default implementation that
+ * UI dispatchers _should not_ override `isDispatchNeeded`, but leave the default implementation that
* returns `true`. To understand the rationale beyond this recommendation, consider the following code:
*
* ```kotlin
@@ -46,24 +46,24 @@
* ```
*
* When you invoke `asyncUpdateUI` in some background thread, it immediately continues to the next
- * line, while UI update happens asynchronously in the UI thread. However, if you invoke
- * it in the UI thread itself, it updates UI _synchronously_ if your `isDispatchNeeded` is
+ * line, while the UI update happens asynchronously in the UI thread. However, if you invoke
+ * it in the UI thread itself, it will update the UI _synchronously_ if your `isDispatchNeeded` is
* overridden with a thread check. Checking if we are already in the UI thread seems more
* efficient (and it might indeed save a few CPU cycles), but this subtle and context-sensitive
* difference in behavior makes the resulting async code harder to debug.
*
- * Basically, the choice here is between "JS-style" asynchronous approach (async actions
- * are always postponed to be executed later in the even dispatch thread) and "C#-style" approach
+ * Basically, the choice here is between the "JS-style" asynchronous approach (async actions
+ * are always postponed to be executed later in the event dispatch thread) and "C#-style" approach
* (async actions are executed in the invoker thread until the first suspension point).
- * While, C# approach seems to be more efficient, it ends up with recommendations like
- * "use `yield` if you need to ....". This is error-prone. JS-style approach is more consistent
+ * While the C# approach seems to be more efficient, it ends up with recommendations like
+ * "use `yield` if you need to ....". This is error-prone. The JS-style approach is more consistent
* and does not require programmers to think about whether they need to yield or not.
*
* However, coroutine builders like [launch][CoroutineScope.launch] and [async][CoroutineScope.async] accept an optional [CoroutineStart]
- * parameter that allows one to optionally choose C#-style [CoroutineStart.UNDISPATCHED] behaviour
+ * parameter that allows one to optionally choose the C#-style [CoroutineStart.UNDISPATCHED] behavior
* whenever it is needed for efficiency.
*
- * This method should be generally exception-safe, an exception thrown from this method
+ * This method should generally be exception-safe. An exception thrown from this method
* may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state.
*
* **Note: This is an experimental api.** Execution semantics of coroutines may change in the future when this function returns `false`.
@@ -74,18 +74,18 @@
/**
* Dispatches execution of a runnable [block] onto another thread in the given [context].
*
- * This method should be generally exception-safe, an exception thrown from this method
+ * This method should generally be exception-safe. An exception thrown from this method
* may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state.
*/
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
/**
- * Dispatches execution of a runnable [block] onto another thread in the given [context]
- * with a hint for dispatcher that current dispatch is triggered by [yield] call, so execution of this
+ * Dispatches execution of a runnable `block` onto another thread in the given `context`
+ * with a hint for the dispatcher that the current dispatch is triggered by a [yield] call, so that the execution of this
* continuation may be delayed in favor of already dispatched coroutines.
*
- * **Implementation note** though yield marker may be passed as a part of [context], this
- * is a separate method for performance reasons
+ * **Implementation note:** Though the `yield` marker may be passed as a part of [context], this
+ * is a separate method for performance reasons.
*
* @suppress **This an internal API and should not be used from general code.**
*/
@@ -93,9 +93,9 @@
public open fun dispatchYield(context: CoroutineContext, block: Runnable) = dispatch(context, block)
/**
- * Returns continuation that wraps the original [continuation], thus intercepting all resumptions.
+ * Returns a continuation that wraps the provided [continuation], thus intercepting all resumptions.
*
- * This method should be generally exception-safe, an exception thrown from this method
+ * This method should generally be exception-safe. An exception thrown from this method
* may leave the coroutines that use this dispatcher in the inconsistent and hard to debug state.
*/
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
@@ -104,13 +104,13 @@
/**
* @suppress **Error**: Operator '+' on two CoroutineDispatcher objects is meaningless.
* CoroutineDispatcher is a coroutine context element and `+` is a set-sum operator for coroutine contexts.
- * The dispatcher to the right of `+` just replaces the dispatcher the left of `+`.
+ * The dispatcher to the right of `+` just replaces the dispatcher to the left.
*/
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated(
message = "Operator '+' on two CoroutineDispatcher objects is meaningless. " +
"CoroutineDispatcher is a coroutine context element and `+` is a set-sum operator for coroutine contexts. " +
- "The dispatcher to the right of `+` just replaces the dispatcher the left of `+`.",
+ "The dispatcher to the right of `+` just replaces the dispatcher to the left.",
level = DeprecationLevel.ERROR
)
public operator fun plus(other: CoroutineDispatcher) = other
diff --git a/kotlinx-coroutines-core/common/src/Yield.kt b/kotlinx-coroutines-core/common/src/Yield.kt
index 78ab27f..2272352 100644
--- a/kotlinx-coroutines-core/common/src/Yield.kt
+++ b/kotlinx-coroutines-core/common/src/Yield.kt
@@ -8,12 +8,12 @@
import kotlin.coroutines.intrinsics.*
/**
- * Yields a thread (or thread pool) of the current coroutine dispatcher to other coroutines to run.
- * If the coroutine dispatcher does not have its own thread pool (like [Dispatchers.Unconfined]) then this
- * function does nothing, but checks if the coroutine [Job] was completed.
+ * Yields the thread (or thread pool) of the current coroutine dispatcher to other coroutines to run.
+ * If the coroutine dispatcher does not have its own thread pool (like [Dispatchers.Unconfined]), this
+ * function does nothing but check if the coroutine's [Job] was completed.
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed when this suspending function is invoked or while
- * this function is waiting for dispatching, it resumes with [CancellationException].
+ * this function is waiting for dispatch, it resumes with a [CancellationException].
*/
public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
val context = uCont.context
diff --git a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
index 7b8f96b..a3be3ba 100644
--- a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
@@ -160,14 +160,24 @@
val result = offerInternal(element)
return when {
result === OFFER_SUCCESS -> true
- // We should check for closed token on offer as well, otherwise offer won't be linearizable
- // in the face of concurrent close()
- result === OFFER_FAILED -> throw closedForSend?.sendException?.let { recoverStackTrace(it) } ?: return false
- result is Closed<*> -> throw recoverStackTrace(result.sendException)
+ result === OFFER_FAILED -> {
+ // We should check for closed token on offer as well, otherwise offer won't be linearizable
+ // in the face of concurrent close()
+ // See https://github.com/Kotlin/kotlinx.coroutines/issues/359
+ throw recoverStackTrace(helpCloseAndGetSendException(closedForSend ?: return false))
+ }
+ result is Closed<*> -> throw recoverStackTrace(helpCloseAndGetSendException(result))
else -> error("offerInternal returned $result")
}
}
+ private fun helpCloseAndGetSendException(closed: Closed<*>): Throwable {
+ // To ensure linearizablity we must ALWAYS help close the channel when we observe that it was closed
+ // See https://github.com/Kotlin/kotlinx.coroutines/issues/1419
+ helpClose(closed)
+ return closed.sendException
+ }
+
private suspend fun sendSuspend(element: E): Unit = suspendAtomicCancellableCoroutine sc@ { cont ->
loop@ while (true) {
if (full) {
@@ -179,8 +189,7 @@
return@sc
}
enqueueResult is Closed<*> -> {
- helpClose(enqueueResult)
- cont.resumeWithException(enqueueResult.sendException)
+ cont.helpCloseAndResumeWithSendException(enqueueResult)
return@sc
}
enqueueResult === ENQUEUE_FAILED -> {} // try to offer instead
@@ -197,8 +206,7 @@
}
offerResult === OFFER_FAILED -> continue@loop
offerResult is Closed<*> -> {
- helpClose(offerResult)
- cont.resumeWithException(offerResult.sendException)
+ cont.helpCloseAndResumeWithSendException(offerResult)
return@sc
}
else -> error("offerInternal returned $offerResult")
@@ -206,6 +214,11 @@
}
}
+ private fun Continuation<*>.helpCloseAndResumeWithSendException(closed: Closed<*>) {
+ helpClose(closed)
+ resumeWithException(closed.sendException)
+ }
+
/**
* Result is:
* * null -- successfully enqueued
@@ -230,23 +243,17 @@
public override fun close(cause: Throwable?): Boolean {
val closed = Closed<E>(cause)
-
/*
* Try to commit close by adding a close token to the end of the queue.
* Successful -> we're now responsible for closing receivers
* Not successful -> help closing pending receivers to maintain invariant
* "if (!close()) next send will throw"
*/
- val closeAdded = queue.addLastIfPrev(closed, { it !is Closed<*> })
- if (!closeAdded) {
- val actualClosed = queue.prevNode as Closed<*>
- helpClose(actualClosed)
- return false
- }
-
- helpClose(closed)
- invokeOnCloseHandler(cause)
- return true
+ val closeAdded = queue.addLastIfPrev(closed) { it !is Closed<*> }
+ val actuallyClosed = if (closeAdded) closed else queue.prevNode as Closed<*>
+ helpClose(actuallyClosed)
+ if (closeAdded) invokeOnCloseHandler(cause)
+ return closeAdded // true if we have closed
}
private fun invokeOnCloseHandler(cause: Throwable?) {
@@ -370,10 +377,7 @@
select.disposeOnSelect(node)
return
}
- enqueueResult is Closed<*> -> {
- helpClose(enqueueResult)
- throw recoverStackTrace(enqueueResult.sendException)
- }
+ enqueueResult is Closed<*> -> throw recoverStackTrace(helpCloseAndGetSendException(enqueueResult))
enqueueResult === ENQUEUE_FAILED -> {} // try to offer
enqueueResult is Receive<*> -> {} // try to offer
else -> error("enqueueSend returned $enqueueResult ")
@@ -388,10 +392,7 @@
block.startCoroutineUnintercepted(receiver = this, completion = select.completion)
return
}
- offerResult is Closed<*> -> {
- helpClose(offerResult)
- throw recoverStackTrace(offerResult.sendException)
- }
+ offerResult is Closed<*> -> throw recoverStackTrace(helpCloseAndGetSendException(offerResult))
else -> error("offerSelectInternal returned $offerResult")
}
}
@@ -432,7 +433,7 @@
private class SendSelect<E, R>(
override val pollResult: Any?,
- @JvmField val channel: SendChannel<E>,
+ @JvmField val channel: AbstractSendChannel<E>,
@JvmField val select: SelectInstance<R>,
@JvmField val block: suspend (SendChannel<E>) -> R
) : Send(), DisposableHandle {
diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt
index f13a15c..07e05f0 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt
@@ -22,8 +22,8 @@
*/
public interface SendChannel<in E> {
/**
- * Returns `true` if this channel was closed by invocation of [close] and thus
- * the [send] and [offer] attempts throws exception.
+ * Returns `true` if this channel was closed by an invocation of [close]. This means that
+ * calling [send] or [offer] will result in an exception.
*
* **Note: This is an experimental api.** This property may change its semantics and/or name in the future.
*/
@@ -31,8 +31,8 @@
public val isClosedForSend: Boolean
/**
- * Returns `true` if the channel is full (out of capacity) and the [send] attempt will suspend.
- * This function returns `false` for [isClosedForSend] channel.
+ * Returns `true` if the channel is full (out of capacity), which means that an attempt to [send] will suspend.
+ * This function returns `false` if the channel [is closed for `send`][isClosedForSend].
*
* @suppress **Will be removed in next releases, no replacement.**
*/
@@ -41,74 +41,76 @@
public val isFull: Boolean
/**
- * Adds [element] into to this channel, suspending the caller while the buffer of this channel is full
- * or if it does not exist, or throws exception if the channel [isClosedForSend] (see [close] for details).
+ * Sends the specified [element] to this channel, suspending the caller while the buffer of this channel is full
+ * or if it does not exist, or throws an exception if the channel [is closed for `send`][isClosedForSend] (see [close] for details).
*
- * Note that closing a channel _after_ this function had suspended does not cause this suspended send invocation
+ * Note that closing a channel _after_ this function has suspended does not cause this suspended [send] invocation
* to abort, because closing a channel is conceptually like sending a special "close token" over this channel.
- * All elements that are sent over the channel are delivered in first-in first-out order. The element that
- * is being sent will get delivered to receivers before a close token.
+ * All elements sent over the channel are delivered in first-in first-out order. The sent element
+ * will be delivered to receivers before the close token.
*
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
- * function is suspended, this function immediately resumes with [CancellationException].
+ * function is suspended, this function immediately resumes with a [CancellationException].
*
- * *Cancellation of suspended send is atomic* -- when this function
- * throws [CancellationException] it means that the [element] was not sent to this channel.
+ * *Cancellation of suspended `send` is atomic*: when this function
+ * throws a [CancellationException], it means that the [element] was not sent to this channel.
* As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
- * continue to execute even after it was cancelled from the same thread in the case when this send operation
+ * continue to execute even after it was cancelled from the same thread in the case when this `send` operation
* was already resumed and the continuation was posted for execution to the thread's queue.
*
* Note that this function does not check for cancellation when it is not suspended.
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
*
- * This function can be used in [select] invocation with [onSend] clause.
+ * This function can be used in [select] invocations with the [onSend] clause.
* Use [offer] to try sending to this channel without waiting.
*/
public suspend fun send(element: E)
/**
- * Clause for [select] expression of [send] suspending function that selects when the element that is specified
- * as parameter is sent to the channel. When the clause is selected the reference to this channel
+ * Clause for the [select] expression of the [send] suspending function that selects when the element that is specified
+ * as the parameter is sent to the channel. When the clause is selected, the reference to this channel
* is passed into the corresponding block.
*
- * The [select] invocation fails with exception if the channel [isClosedForSend] (see [close] for details).
+ * The [select] invocation fails with an exception if the channel [is closed for `send`][isClosedForSend] (see [close] for details).
*/
public val onSend: SelectClause2<E, SendChannel<E>>
/**
- * Adds [element] into this queue if it is possible to do so immediately without violating capacity restrictions
- * and returns `true`. Otherwise, it returns `false` immediately
- * or throws exception if the channel [isClosedForSend] (see [close] for details).
+ * Immediately adds the specified [element] to this channel, if this doesn't violate its capacity restrictions,
+ * and returns `true`. Otherwise, just returns `false`. This is a synchronous variant of [send] which backs off
+ * in situations when `send` suspends.
+ *
+ * Throws an exception if the channel [is closed for `send`][isClosedForSend] (see [close] for details).
*/
public fun offer(element: E): Boolean
/**
* Closes this channel.
- * This is an idempotent operation -- subsequent invocations of this function have no effect and return `false`.
+ * This is an idempotent operation — subsequent invocations of this function have no effect and return `false`.
* Conceptually, its sends a special "close token" over this channel.
*
- * Immediately after invocation of this function
+ * Immediately after invocation of this function,
* [isClosedForSend] starts returning `true`. However, [isClosedForReceive][ReceiveChannel.isClosedForReceive]
* on the side of [ReceiveChannel] starts returning `true` only after all previously sent elements
* are received.
*
- * A channel that was closed without a [cause] throws [ClosedSendChannelException] on attempts to send
- * and [ClosedReceiveChannelException] on attempts to receive.
+ * A channel that was closed without a [cause] throws a [ClosedSendChannelException] on attempts to [send] or [offer]
+ * and [ClosedReceiveChannelException] on attempts to [receive][ReceiveChannel.receive].
* A channel that was closed with non-null [cause] is called a _failed_ channel. Attempts to send or
* receive on a failed channel throw the specified [cause] exception.
*/
public fun close(cause: Throwable? = null): Boolean
/**
- * Registers handler which is synchronously invoked once the channel is [closed][close]
- * or receiving side of this channel is [cancelled][ReceiveChannel.cancel].
- * Only one handler can be attached to the channel during channel's lifetime.
- * Handler is invoked when [isClosedForSend] starts to return `true`.
- * If channel is already closed, handler is invoked immediately.
+ * Registers a [handler] which is synchronously invoked once the channel is [closed][close]
+ * or the receiving side of this channel is [cancelled][ReceiveChannel.cancel].
+ * Only one handler can be attached to a channel during its lifetime.
+ * The `handler` is invoked when [isClosedForSend] starts to return `true`.
+ * If the channel is closed already, the handler is invoked immediately.
*
* The meaning of `cause` that is passed to the handler:
- * * `null` if channel was closed or cancelled without corresponding argument
- * * close or cancel cause otherwise.
+ * * `null` if the channel was closed or cancelled without the corresponding argument
+ * * the cause of `close` or `cancel` otherwise.
*
* Example of usage (exception handling is omitted):
* ```
@@ -128,7 +130,7 @@
*
* **Note: This is an experimental api.** This function may change its semantics, parameters or return type in the future.
*
- * @throws UnsupportedOperationException if underlying channel doesn't support [invokeOnClose].
+ * @throws UnsupportedOperationException if the underlying channel doesn't support [invokeOnClose].
* Implementation note: currently, [invokeOnClose] is unsupported only by Rx-like integrations
*
* @throws IllegalStateException if another handler was already registered
@@ -143,9 +145,9 @@
public interface ReceiveChannel<out E> {
/**
* Returns `true` if this channel was closed by invocation of [close][SendChannel.close] on the [SendChannel]
- * side and all previously sent items were already received, so that the [receive] attempt
- * throws [ClosedReceiveChannelException]. If the channel was closed because of the exception, it
- * is considered closed, too, but it is called a _failed_ channel. All suspending attempts to receive
+ * side and all previously sent items were already received. This means that calling [receive]
+ * will result in a [ClosedReceiveChannelException]. If the channel was closed because of an exception, it
+ * is considered closed, too, but is called a _failed_ channel. All suspending attempts to receive
* an element from a failed channel throw the original [close][SendChannel.close] cause exception.
*
* **Note: This is an experimental api.** This property may change its semantics and/or name in the future.
@@ -154,61 +156,61 @@
public val isClosedForReceive: Boolean
/**
- * Returns `true` if the channel is empty (contains no elements) and the [receive] attempt will suspend.
- * This function returns `false` for [isClosedForReceive] channel.
+ * Returns `true` if the channel is empty (contains no elements), which means that an attempt to [receive] will suspend.
+ * This function returns `false` if the channel [is closed for `receive`][isClosedForReceive].
*/
@ExperimentalCoroutinesApi
public val isEmpty: Boolean
/**
- * Retrieves and removes the element from this channel suspending the caller while this channel is empty,
- * or throws [ClosedReceiveChannelException] if the channel [isClosedForReceive].
- * If the channel was closed because of the exception, it is called a _failed_ channel and this function
- * throws the original [close][SendChannel.close] cause exception.
+ * Retrieves and removes an element from this channel if it's not empty, or suspends the caller while the channel is empty,
+ * or throws a [ClosedReceiveChannelException] if the channel [is closed for `receive`][isClosedForReceive].
+ * If the channel was closed because of an exception, it is called a _failed_ channel and this function
+ * will throw the original [close][SendChannel.close] cause exception.
*
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
- * function is suspended, this function immediately resumes with [CancellationException].
+ * function is suspended, this function immediately resumes with a [CancellationException].
*
- * *Cancellation of suspended receive is atomic* -- when this function
- * throws [CancellationException] it means that the element was not retrieved from this channel.
+ * *Cancellation of suspended `receive` is atomic*: when this function
+ * throws a [CancellationException], it means that the element was not retrieved from this channel.
* As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
- * continue to execute even after it was cancelled from the same thread in the case when this receive operation
+ * continue to execute even after it was cancelled from the same thread in the case when this `receive` operation
* was already resumed and the continuation was posted for execution to the thread's queue.
*
* Note that this function does not check for cancellation when it is not suspended.
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
*
- * This function can be used in [select] invocation with [onReceive] clause.
+ * This function can be used in [select] invocations with the [onReceive] clause.
* Use [poll] to try receiving from this channel without waiting.
*/
public suspend fun receive(): E
/**
- * Clause for [select] expression of [receive] suspending function that selects with the element that
- * is received from the channel.
- * The [select] invocation fails with exception if the channel
- * [isClosedForReceive] (see [close][SendChannel.close] for details).
+ * Clause for the [select] expression of the [receive] suspending function that selects with the element
+ * received from the channel.
+ * The [select] invocation fails with an exception if the channel
+ * [is closed for `receive`][isClosedForReceive] (see [close][SendChannel.close] for details).
*/
public val onReceive: SelectClause1<E>
/**
- * Retrieves and removes the element from this channel suspending the caller while this channel is empty,
- * or returns `null` if the channel is [closed][isClosedForReceive] without cause
+ * Retrieves and removes an element from this channel if it's not empty, or suspends the caller while the channel is empty,
+ * or returns `null` if the channel is [closed for `receive`][isClosedForReceive] without cause,
* or throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
*
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
- * function is suspended, this function immediately resumes with [CancellationException].
+ * function is suspended, this function immediately resumes with a [CancellationException].
*
- * *Cancellation of suspended receive is atomic* -- when this function
- * throws [CancellationException] it means that the element was not retrieved from this channel.
+ * *Cancellation of suspended `receive` is atomic*: when this function
+ * throws a [CancellationException], it means that the element was not retrieved from this channel.
* As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
- * continue to execute even after it was cancelled from the same thread in the case when this receive operation
+ * continue to execute even after it was cancelled from the same thread in the case when this `receive` operation
* was already resumed and the continuation was posted for execution to the thread's queue.
*
* Note that this function does not check for cancellation when it is not suspended.
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
*
- * This function can be used in [select] invocation with [onReceiveOrNull] clause.
+ * This function can be used in [select] invocations with the [onReceiveOrNull] clause.
* Use [poll] to try receiving from this channel without waiting.
*
* @suppress **Deprecated**: in favor of receiveOrClosed and receiveOrNull extension.
@@ -224,9 +226,9 @@
public suspend fun receiveOrNull(): E?
/**
- * Clause for [select] expression of [receiveOrNull] suspending function that selects with the element that
- * is received from the channel or selects with `null` if the channel
- * [isClosedForReceive] without cause. The [select] invocation fails with
+ * Clause for the [select] expression of the [receiveOrNull] suspending function that selects with the element
+ * received from the channel or `null` if the channel is
+ * [closed for `receive`][isClosedForReceive] without a cause. The [select] invocation fails with
* the original [close][SendChannel.close] cause exception if the channel has _failed_.
*
* @suppress **Deprecated**: in favor of onReceiveOrClosed and onReceiveOrNull extension.
@@ -242,23 +244,23 @@
public val onReceiveOrNull: SelectClause1<E?>
/**
- * Retrieves and removes the element from this channel suspending the caller while this channel [isEmpty].
- * This method returns [ValueOrClosed] with a value if element was successfully retrieved from the channel
- * or [ValueOrClosed] with close cause if channel was closed.
+ * Retrieves and removes an element from this channel if it's not empty, or suspends the caller while this channel is empty.
+ * This method returns [ValueOrClosed] with the value of an element successfully retrieved from the channel
+ * or the close cause if the channel was closed.
*
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
- * function is suspended, this function immediately resumes with [CancellationException].
+ * function is suspended, this function immediately resumes with a [CancellationException].
*
- * *Cancellation of suspended receive is atomic* -- when this function
- * throws [CancellationException] it means that the element was not retrieved from this channel.
+ * *Cancellation of suspended `receive` is atomic*: when this function
+ * throws a [CancellationException], it means that the element was not retrieved from this channel.
* As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
* continue to execute even after it was cancelled from the same thread in the case when this receive operation
* was already resumed and the continuation was posted for execution to the thread's queue.
*
- * Note, that this function does not check for cancellation when it is not suspended.
+ * Note that this function does not check for cancellation when it is not suspended.
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
*
- * This function can be used in [select] invocation with [onReceiveOrClosed] clause.
+ * This function can be used in [select] invocations with the [onReceiveOrClosed] clause.
* Use [poll] to try receiving from this channel without waiting.
*
* @suppress *This is an internal API, do not use*: Inline classes ABI is not stable yet and
@@ -268,9 +270,9 @@
public suspend fun receiveOrClosed(): ValueOrClosed<E>
/**
- * Clause for [select] expression of [receiveOrClosed] suspending function that selects with the [ValueOrClosed] with a value
- * that is received from the channel or selects with [ValueOrClosed] with a close cause if the channel
- * [isClosedForReceive].
+ * Clause for the [select] expression of the [receiveOrClosed] suspending function that selects with the [ValueOrClosed] with a value
+ * that is received from the channel or with a close cause if the channel
+ * [is closed for `receive`][isClosedForReceive].
*
* @suppress *This is an internal API, do not use*: Inline classes ABI is not stable yet and
* [KT-27524](https://youtrack.jetbrains.com/issue/KT-27524) needs to be fixed.
@@ -279,15 +281,15 @@
public val onReceiveOrClosed: SelectClause1<ValueOrClosed<E>>
/**
- * Retrieves and removes the element from this channel, or returns `null` if this channel is empty
- * or is [isClosedForReceive] without cause.
+ * Retrieves and removes an element from this channel if its not empty, or returns `null` if the channel is empty
+ * or is [is closed for `receive`][isClosedForReceive] without a cause.
* It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
*/
public fun poll(): E?
/**
- * Returns new iterator to receive elements from this channels using `for` loop.
- * Iteration completes normally when the channel is [isClosedForReceive] without cause and
+ * Returns a new iterator to receive elements from this channel using a `for` loop.
+ * Iteration completes normally when the channel [is closed for `receive`][isClosedForReceive] without a cause and
* throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
*/
public operator fun iterator(): ChannelIterator<E>
@@ -297,14 +299,14 @@
* This function closes the channel and removes all buffered sent elements from it.
*
* A cause can be used to specify an error message or to provide other details on
- * a cancellation reason for debugging purposes.
+ * the cancellation reason for debugging purposes.
* If the cause is not specified, then an instance of [CancellationException] with a
* default message is created to [close][SendChannel.close] the channel.
*
* Immediately after invocation of this function [isClosedForReceive] and
* [isClosedForSend][SendChannel.isClosedForSend]
- * on the side of [SendChannel] start returning `true`. All attempts to send to this channel
- * or receive from this channel will throw [CancellationException].
+ * on the side of [SendChannel] start returning `true`. Any attempt to send to or receive from this channel
+ * will lead to a [CancellationException].
*/
public fun cancel(cause: CancellationException? = null)
@@ -322,8 +324,8 @@
}
/**
- * A discriminated union of [ReceiveChannel.receiveOrClosed] result,
- * that encapsulates either successfully received element of type [T] from the channel or a close cause.
+ * A discriminated union of [ReceiveChannel.receiveOrClosed] result
+ * that encapsulates either an element of type [T] successfully received from the channel or a close cause.
*
* :todo: Do not make it public before resolving todos in the code of this class.
*
@@ -335,36 +337,36 @@
public inline class ValueOrClosed<out T>
internal constructor(private val holder: Any?) {
/**
- * Returns `true` if this instance represents received element.
+ * Returns `true` if this instance represents a received element.
* In this case [isClosed] returns `false`.
* todo: it is commented for now, because it is not used
*/
//public val isValue: Boolean get() = holder !is Closed
/**
- * Returns `true` if this instance represents close cause.
+ * Returns `true` if this instance represents a close cause.
* In this case [isValue] returns `false`.
*/
public val isClosed: Boolean get() = holder is Closed
/**
- * Returns received value if this instance represents received value or throws [IllegalStateException] otherwise.
+ * Returns the received value if this instance represents a received value, or throws an [IllegalStateException] otherwise.
*
- * :todo: Decide if it is needed how it shall be named with relation to [valueOrThrow]:
+ * :todo: Decide, if it is needed, how it shall be named with relation to [valueOrThrow]:
*
- * So we have the following methods on ValueOrClosed: `value`, `valueOrNull`, `valueOrThrow`.
- * On the other hand, the channel has the following receive variants:
+ * So we have the following methods on `ValueOrClosed`: `value`, `valueOrNull`, `valueOrThrow`.
+ * On the other hand, the channel has the following `receive` variants:
* * `receive` which corresponds to `receiveOrClosed().valueOrThrow`... huh?
* * `receiveOrNull` which corresponds to `receiveOrClosed().valueOrNull`
* * `receiveOrClosed`
- * For the sake of consider dropping this version of `value` and rename [valueOrThrow] to simply `value`.
+ * For the sake of simplicity consider dropping this version of `value` and rename [valueOrThrow] to simply `value`.
*/
@Suppress("UNCHECKED_CAST")
public val value: T
get() = if (holder is Closed) error(DEFAULT_CLOSE_MESSAGE) else holder as T
/**
- * Returns received value if this element represents received value or `null` otherwise.
+ * Returns the received value if this element represents a received value, or `null` otherwise.
* :todo: Decide if it shall be made into extension that is available only for non-null T.
* Note: it might become inconsistent with kotlin.Result
*/
@@ -373,9 +375,9 @@
get() = if (holder is Closed) null else holder as T
/**
- * :todo: Decide if it is needed how it shall be named with relation to [value].
- * Note, that valueOrThrow rethrows the cause adding no meaningful information about the callsite,
- * so if one is sure that ValueOrClosed is always value, this very property should be used.
+ * :todo: Decide, if it is needed, how it shall be named with relation to [value].
+ * Note that `valueOrThrow` rethrows the cause adding no meaningful information about the call site,
+ * so if one is sure that `ValueOrClosed` always holds a value, this very property should be used.
* Otherwise, it could be very hard to locate the source of the exception.
* todo: it is commented for now, because it is not used
*/
@@ -384,8 +386,8 @@
// get() = if (holder is Closed) throw holder.exception else holder as T
/**
- * Returns close cause of the channel if this instance represents close cause or throws
- * [IllegalStateException] otherwise.
+ * Returns the close cause of the channel if this instance represents a close cause, or throws
+ * an [IllegalStateException] otherwise.
*/
@Suppress("UNCHECKED_CAST")
public val closeCause: Throwable? get() =
@@ -429,17 +431,17 @@
public interface ChannelIterator<out E> {
/**
* Returns `true` if the channel has more elements, suspending the caller while this channel is empty,
- * or returns `false` if the channel [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause.
+ * or returns `false` if the channel [is closed for `receive`][ReceiveChannel.isClosedForReceive] without a cause.
* It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
*
- * This function retrieves and removes the element from this channel for the subsequent invocation
+ * This function retrieves and removes an element from this channel for the subsequent invocation
* of [next].
*
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
- * function is suspended, this function immediately resumes with [CancellationException].
+ * function is suspended, this function immediately resumes with a [CancellationException].
*
- * *Cancellation of suspended receive is atomic* -- when this function
- * throws [CancellationException] it means that the element was not retrieved from this channel.
+ * *Cancellation of suspended `receive` is atomic*: when this function
+ * throws a [CancellationException], it means that the element was not retrieved from this channel.
* As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
* continue to execute even after it was cancelled from the same thread in the case when this receive operation
* was already resumed and the continuation was posted for execution to the thread's queue.
@@ -463,9 +465,9 @@
}
/**
- * Retrieves the element from the current iterator previously removed from the channel by preceding call to [hasNext] or
- * throws [IllegalStateException] if [hasNext] was not invoked.
- * [next] should only be used in pair with [hasNext]:
+ * Retrieves the element removed from the channel by a preceding call to [hasNext], or
+ * throws an [IllegalStateException] if [hasNext] was not invoked.
+ * This method should only be used in pair with [hasNext]:
* ```
* while (iterator.hasNext()) {
* val element = iterator.next()
@@ -473,64 +475,64 @@
* }
* ```
*
- * This method throws [ClosedReceiveChannelException] if the channel [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause.
+ * This method throws a [ClosedReceiveChannelException] if the channel [is closed for `receive`][ReceiveChannel.isClosedForReceive] without a cause.
* It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
*/
public operator fun next(): E
}
/**
- * Channel is a non-blocking primitive for communication between sender using [SendChannel] and receiver using [ReceiveChannel].
- * Conceptually, a channel is similar to [BlockingQueue][java.util.concurrent.BlockingQueue],
- * but it has suspending operations instead of blocking ones and it can be closed.
+ * Channel is a non-blocking primitive for communication between a sender (via [SendChannel]) and a receiver (via [ReceiveChannel]).
+ * Conceptually, a channel is similar to Java's [BlockingQueue][java.util.concurrent.BlockingQueue],
+ * but it has suspending operations instead of blocking ones and can be [closed][SendChannel.close].
*
- * `Channel(capacity)` factory function is used to create channels of different kind depending on
- * the value of `capacity` integer:
+ * The `Channel(capacity)` factory function is used to create channels of different kinds depending on
+ * the value of the `capacity` integer:
*
- * * When `capacity` is 0 -- it creates `RendezvousChannel`.
- * This channel does not have any buffer at all. An element is transferred from sender
- * to receiver only when [send] and [receive] invocations meet in time (rendezvous), so [send] suspends
- * until another coroutine invokes [receive] and [receive] suspends until another coroutine invokes [send].
+ * * When `capacity` is 0 — it creates a `RendezvousChannel`.
+ * This channel does not have any buffer at all. An element is transferred from the sender
+ * to the receiver only when [send] and [receive] invocations meet in time (rendezvous), so [send] suspends
+ * until another coroutine invokes [receive], and [receive] suspends until another coroutine invokes [send].
*
- * * When `capacity` is [Channel.UNLIMITED] -- it creates `LinkedListChannel`.
- * This is a channel with linked-list buffer of a unlimited capacity (limited only by available memory).
- * Sender to this channel never suspends and [offer] always returns `true`.
+ * * When `capacity` is [Channel.UNLIMITED] — it creates a `LinkedListChannel`.
+ * This channel has a linked-list buffer of unlimited capacity (limited only by available memory).
+ * [Sending][send] to this channel never suspends, and [offer] always returns `true`.
*
- * * When `capacity` is [Channel.CONFLATED] -- it creates `ConflatedChannel`.
+ * * When `capacity` is [Channel.CONFLATED] — it creates a `ConflatedChannel`.
* This channel buffers at most one element and conflates all subsequent `send` and `offer` invocations,
- * so that the receiver always gets the most recently sent element.
- * Back-to-send sent elements are _conflated_ -- only the the most recently sent element is received,
+ * so that the receiver always gets the last element sent.
+ * Back-to-send sent elements are _conflated_ — only the last sent element is received,
* while previously sent elements **are lost**.
- * Sender to this channel never suspends and [offer] always returns `true`.
+ * [Sending][send] to this channel never suspends, and [offer] always returns `true`.
*
- * * When `capacity` is positive, but less than [UNLIMITED] -- it creates array-based channel with given capacity.
+ * * When `capacity` is positive but less than [UNLIMITED] — it creates an array-based channel with the specified capacity.
* This channel has an array buffer of a fixed `capacity`.
- * Sender suspends only when buffer is full and receiver suspends only when buffer is empty.
+ * [Sending][send] suspends only when the buffer is full, and [receiving][receive] suspends only when the buffer is empty.
*/
public interface Channel<E> : SendChannel<E>, ReceiveChannel<E> {
/**
- * Constants for channel factory function `Channel()`.
+ * Constants for the channel factory function `Channel()`.
*/
public companion object Factory {
/**
- * Requests channel with unlimited capacity buffer in `Channel(...)` factory function
+ * Requests a channel with an unlimited capacity buffer in the `Channel(...)` factory function
*/
public const val UNLIMITED = Int.MAX_VALUE
/**
- * Requests rendezvous channel in `Channel(...)` factory function -- the `RendezvousChannel` gets created.
+ * Requests a rendezvous channel in the `Channel(...)` factory function — a `RendezvousChannel` gets created.
*/
public const val RENDEZVOUS = 0
/**
- * Requests conflated channel in `Channel(...)` factory function -- the `ConflatedChannel` gets created.
+ * Requests a conflated channel in the `Channel(...)` factory function — a `ConflatedChannel` gets created.
*/
public const val CONFLATED = -1
/**
- * Requests buffered channel with a default buffer capacity in `Channel(...)` factory function --
- * the `ArrayChannel` gets created with a default capacity.
- * This capacity is equal to 64 by default and can be overridden by setting
+ * Requests a buffered channel with the default buffer capacity in the `Channel(...)` factory function —
+ * an `ArrayChannel` gets created with the default capacity.
+ * The default capacity is 64 and can be overridden by setting
* [DEFAULT_BUFFER_PROPERTY_NAME] on JVM.
*/
public const val BUFFERED = -2
@@ -567,18 +569,18 @@
}
/**
- * Indicates attempt to [send][SendChannel.send] on [isClosedForSend][SendChannel.isClosedForSend] channel
+ * Indicates an attempt to [send][SendChannel.send] to a [isClosedForSend][SendChannel.isClosedForSend] channel
* that was closed without a cause. A _failed_ channel rethrows the original [close][SendChannel.close] cause
* exception on send attempts.
*
- * This exception is a subclass of [IllegalStateException] because, conceptually, sender is responsible
- * for closing the channel and not be trying to send anything after the channel was close. Attempts to
- * send into the closed channel indicate logical error in the sender's code.
+ * This exception is a subclass of [IllegalStateException], because, conceptually, it is the sender's responsibility
+ * to close the channel and not try to send anything thereafter. Attempts to
+ * send to a closed channel indicate a logical error in the sender's code.
*/
public class ClosedSendChannelException(message: String?) : IllegalStateException(message)
/**
- * Indicates attempt to [receive][ReceiveChannel.receive] on [isClosedForReceive][ReceiveChannel.isClosedForReceive]
+ * Indicates an attempt to [receive][ReceiveChannel.receive] from a [isClosedForReceive][ReceiveChannel.isClosedForReceive]
* channel that was closed without a cause. A _failed_ channel rethrows the original [close][SendChannel.close] cause
* exception on receive attempts.
*
diff --git a/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt b/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt
index 3afc86c..2a73930 100644
--- a/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.channels
@@ -29,8 +29,7 @@
when {
result === OFFER_SUCCESS -> return OFFER_SUCCESS
result === OFFER_FAILED -> { // try to buffer
- val sendResult = sendBuffered(element)
- when (sendResult) {
+ when (val sendResult = sendBuffered(element)) {
null -> return OFFER_SUCCESS
is Closed<*> -> return sendResult
}
diff --git a/kotlinx-coroutines-core/common/src/channels/Produce.kt b/kotlinx-coroutines-core/common/src/channels/Produce.kt
index a579d7a..59ebf52 100644
--- a/kotlinx-coroutines-core/common/src/channels/Produce.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Produce.kt
@@ -8,19 +8,19 @@
import kotlin.coroutines.*
/**
- * Scope for [produce][CoroutineScope.produce] coroutine builder.
+ * Scope for the [produce][CoroutineScope.produce] coroutine builder.
*
- * **Note: This is an experimental api.** Behaviour of producers that work as children in a parent scope with respect
+ * **Note: This is an experimental api.** Behavior of producers that work as children in a parent scope with respect
* to cancellation and error handling may change in the future.
*/
@ExperimentalCoroutinesApi
public interface ProducerScope<in E> : CoroutineScope, SendChannel<E> {
/**
- * A reference to the channel that this coroutine [sends][send] elements to.
+ * A reference to the channel this coroutine [sends][send] elements to.
* It is provided for convenience, so that the code in the coroutine can refer
- * to the channel as `channel` as apposed to `this`.
+ * to the channel as `channel` as opposed to `this`.
* All the [SendChannel] functions on this interface delegate to
- * the channel instance returned by this function.
+ * the channel instance returned by this property.
*/
val channel: SendChannel<E>
}
@@ -29,9 +29,9 @@
* Suspends the current coroutine until the channel is either [closed][SendChannel.close] or [cancelled][ReceiveChannel.cancel]
* and invokes the given [block] before resuming the coroutine.
*
- * Note that when producer channel is cancelled this function resumes with cancellation exception,
- * so putting the code after calling this function would not lead to its execution in case of cancellation.
- * That is why this code takes a lambda parameter.
+ * Note that when the producer channel is cancelled, this function resumes with a cancellation exception.
+ * Therefore, in case of cancellation, no code after the call to this function will be executed.
+ * That's why this function takes a lambda parameter.
*
* Example of usage:
* ```
@@ -43,7 +43,7 @@
*/
@ExperimentalCoroutinesApi
public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {
- check(kotlin.coroutines.coroutineContext[Job] === this) { "awaitClose() can be invoke only from the producer context" }
+ check(kotlin.coroutines.coroutineContext[Job] === this) { "awaitClose() can only be invoked from the producer context" }
try {
suspendCancellableCoroutine<Unit> { cont ->
invokeOnClose {
@@ -56,28 +56,28 @@
}
/**
- * Launches new coroutine to produce a stream of values by sending them to a channel
+ * Launches a new coroutine to produce a stream of values by sending them to a channel
* and returns a reference to the coroutine as a [ReceiveChannel]. This resulting
* object can be used to [receive][ReceiveChannel.receive] elements produced by this coroutine.
*
- * The scope of the coroutine contains [ProducerScope] interface, which implements
- * both [CoroutineScope] and [SendChannel], so that coroutine can invoke
+ * The scope of the coroutine contains the [ProducerScope] interface, which implements
+ * both [CoroutineScope] and [SendChannel], so that the coroutine can invoke
* [send][SendChannel.send] directly. The channel is [closed][SendChannel.close]
* when the coroutine completes.
* The running coroutine is cancelled when its receive channel is [cancelled][ReceiveChannel.cancel].
*
- * Coroutine context is inherited from a [CoroutineScope], additional context elements can be specified with [context] argument.
- * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [Dispatchers.Default] is used.
- * The parent job is inherited from a [CoroutineScope] as well, but it can also be overridden
- * with corresponding [coroutineContext] element.
+ * The coroutine context is inherited from this [CoroutineScope]. Additional context elements can be specified with the [context] argument.
+ * If the context does not have any dispatcher or other [ContinuationInterceptor], then [Dispatchers.Default] is used.
+ * The parent job is inherited from the [CoroutineScope] as well, but it can also be overridden
+ * with a corresponding [coroutineContext] element.
*
- * Uncaught exceptions in this coroutine close the channel with this exception as a cause and
- * the resulting channel becomes _failed_, so that any attempt to receive from such a channel throws exception.
+ * Any uncaught exception in this coroutine will close the channel with this exception as the cause and
+ * the resulting channel will become _failed_, so that any attempt to receive from it thereafter will throw an exception.
*
* The kind of the resulting channel depends on the specified [capacity] parameter.
- * See [Channel] interface documentation for details.
+ * See the [Channel] interface documentation for details.
*
- * See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
+ * See [newCoroutineContext] for a description of debugging facilities available for newly created coroutines.
*
* **Note: This is an experimental api.** Behaviour of producers that work as children in a parent scope with respect
* to cancellation and error handling may change in the future.
@@ -100,9 +100,9 @@
}
/**
- * This an internal API and should not be used from general code.**
- * onCompletion parameter will be redesigned.
- * If you have to use `onCompletion` operator, please report to https://github.com/Kotlin/kotlinx.coroutines/issues/.
+ * **This is an internal API and should not be used from general code.**
+ * The `onCompletion` parameter will be redesigned.
+ * If you have to use the `onCompletion` operator, please report to https://github.com/Kotlin/kotlinx.coroutines/issues/.
* As a temporary solution, [invokeOnCompletion][Job.invokeOnCompletion] can be used instead:
* ```
* fun <E> ReceiveChannel<E>.myOperator(): ReceiveChannel<E> = GlobalScope.produce(Dispatchers.Unconfined) {
diff --git a/kotlinx-coroutines-core/common/src/flow/Builders.kt b/kotlinx-coroutines-core/common/src/flow/Builders.kt
index 2b03e33..4b9fa6b 100644
--- a/kotlinx-coroutines-core/common/src/flow/Builders.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Builders.kt
@@ -20,17 +20,18 @@
*
* Example of usage:
* ```
- * fun fibonacci(): Flow<Long> = flow {
- * emit(1L)
- * var f1 = 1L
- * var f2 = 1L
- * repeat(100) {
- * var tmp = f1
- * f1 = f2
- * f2 += tmp
- * emit(f1)
+ * fun fibonacci(): Flow<BigInteger> = flow {
+ * var x = BigInteger.ZERO
+ * var y = BigInteger.ONE
+ * while (true) {
+ * emit(x)
+ * x = y.also {
+ * y += x
+ * }
* }
* }
+ *
+ * fibonacci().take(100).collect { println(it) }
* ```
*
* `emit` should happen strictly in the dispatchers of the [block] in order to preserve the flow context.
@@ -85,7 +86,7 @@
}
/**
- * Creates a flow that produces values from the given iterable.
+ * Creates a flow that produces values from the given iterator.
*/
public fun <T> Iterator<T>.asFlow(): Flow<T> = flow {
forEach { value ->
@@ -103,7 +104,12 @@
}
/**
- * Creates a flow that produces values from the given array of elements.
+ * Creates a flow that produces values from the specified `vararg`-arguments.
+ *
+ * Example of usage:
+ * ```
+ * flowOf(1, 2, 3)
+ * ```
*/
public fun <T> flowOf(vararg elements: T): Flow<T> = flow {
for (element in elements) {
@@ -112,12 +118,12 @@
}
/**
- * Creates flow that produces a given [value].
+ * Creates flow that produces the given [value].
*/
public fun <T> flowOf(value: T): Flow<T> = flow {
/*
* Implementation note: this is just an "optimized" overload of flowOf(vararg)
- * which significantly reduce the footprint of widespread single-value flows.
+ * which significantly reduces the footprint of widespread single-value flows.
*/
emit(value)
}
@@ -141,7 +147,7 @@
}
/**
- * Creates flow that produces values from the given array.
+ * Creates a flow that produces values from the array.
*/
public fun IntArray.asFlow(): Flow<Int> = flow {
forEach { value ->
@@ -150,7 +156,7 @@
}
/**
- * Creates flow that produces values from the given array.
+ * Creates a flow that produces values from the array.
*/
public fun LongArray.asFlow(): Flow<Long> = flow {
forEach { value ->
@@ -159,7 +165,7 @@
}
/**
- * Creates flow that produces values from the given range.
+ * Creates a flow that produces values from the range.
*/
public fun IntRange.asFlow(): Flow<Int> = flow {
forEach { value ->
@@ -168,7 +174,7 @@
}
/**
- * Creates flow that produces values from the given range.
+ * Creates a flow that produces values from the range.
*/
public fun LongRange.asFlow(): Flow<Long> = flow {
forEach { value ->
@@ -197,20 +203,20 @@
/**
* Creates an instance of the cold [Flow] with elements that are sent to a [SendChannel]
- * that is provided to the builder's [block] of code via [ProducerScope]. It allows elements to be
- * produced by the code that is running in a different context or running concurrently.
- * The resulting flow is _cold_, which means that [block] is called on each call of a terminal operator
- * on the resulting flow.
+ * provided to the builder's [block] of code via [ProducerScope]. It allows elements to be
+ * produced by code that is running in a different context or concurrently.
+ * The resulting flow is _cold_, which means that [block] is called every time a terminal operator
+ * is applied to the resulting flow.
*
* This builder ensures thread-safety and context preservation, thus the provided [ProducerScope] can be used
* concurrently from different contexts.
- * The resulting flow completes as soon as the code in the [block] and all its children complete.
+ * The resulting flow completes as soon as the code in the [block] and all its children completes.
* Use [awaitClose] as the last statement to keep it running.
- * For more detailed example please refer to [callbackFlow] documentation.
+ * A more detailed example is provided in the documentation of [callbackFlow].
*
- * A channel with [default][Channel.BUFFERED] buffer size is used. Use [buffer] operator on the
- * resulting flow to specify a value other than default and to control what happens when data is produced faster
- * than it is consumed, that is to control backpressure behavior.
+ * A channel with the [default][Channel.BUFFERED] buffer size is used. Use the [buffer] operator on the
+ * resulting flow to specify a user-defined value and to control what happens when data is produced faster
+ * than consumed, i.e. to control the back-pressure behavior.
*
* Adjacent applications of [channelFlow], [flowOn], [buffer], [produceIn], and [broadcastIn] are
* always fused so that only one properly configured channel is used for execution.
@@ -245,22 +251,22 @@
/**
* Creates an instance of the cold [Flow] with elements that are sent to a [SendChannel]
- * that is provided to the builder's [block] of code via [ProducerScope]. It allows elements to be
- * produced by the code that is running in a different context or running concurrently.
+ * provided to the builder's [block] of code via [ProducerScope]. It allows elements to be
+ * produced by code that is running in a different context or concurrently.
*
- * The resulting flow is _cold_, which means that [block] is called on each call of a terminal operator
- * on the resulting flow.
+ * The resulting flow is _cold_, which means that [block] is called every time a terminal operator
+ * is applied to the resulting flow.
*
* This builder ensures thread-safety and context preservation, thus the provided [ProducerScope] can be used
- * from any context, e.g. from the callback-based API.
- * The resulting flow completes as soon as the code in the [block] and all its children complete.
+ * from any context, e.g. from a callback-based API.
+ * The resulting flow completes as soon as the code in the [block] and all its children completes.
* Use [awaitClose] as the last statement to keep it running.
- * [awaitClose] argument is called when either flow consumer cancels flow collection
- * or when callback-based API invokes [SendChannel.close] manually.
+ * The [awaitClose] argument is called either when a flow consumer cancels the flow collection
+ * or when a callback-based API invokes [SendChannel.close] manually.
*
- * A channel with [default][Channel.BUFFERED] buffer size is used. Use [buffer] operator on the
- * resulting flow to specify a value other than default and to control what happens when data is produced faster
- * than it is consumed, that is to control backpressure behavior.
+ * A channel with the [default][Channel.BUFFERED] buffer size is used. Use the [buffer] operator on the
+ * resulting flow to specify a user-defined value and to control what happens when data is produced faster
+ * than consumed, i.e. to control the back-pressure behavior.
*
* Adjacent applications of [callbackFlow], [flowOn], [buffer], [produceIn], and [broadcastIn] are
* always fused so that only one properly configured channel is used for execution.
diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt
index 67bd68a..75e34e5 100644
--- a/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelLFStressTest.kt
@@ -50,9 +50,12 @@
}
private fun performLockFreedomTest() {
- env.onCompletion { channel.close() }
- repeat(2) { env.testThread { sender() } }
- repeat(2) { env.testThread { receiver() } }
+ env.onCompletion {
+ // We must cancel the channel to abort both senders & receivers
+ channel.cancel(TestCompleted())
+ }
+ repeat(2) { env.testThread("sender-$it") { sender() } }
+ repeat(2) { env.testThread("receiver-$it") { receiver() } }
env.performTest(nSeconds) {
println("Sent: $sendIndex, Received: $receiveCount, dups: $duplicateCount")
}
@@ -70,7 +73,7 @@
val value = sendIndex.getAndIncrement()
try {
channel.send(value)
- } catch (e: ClosedSendChannelException) {
+ } catch (e: TestCompleted) {
check(env.isCompleted) // expected when test was completed
markReceived(value) // fake received (actually failed to send)
}
@@ -79,7 +82,7 @@
private suspend fun receiver() {
val value = try {
channel.receive()
- } catch (e: ClosedReceiveChannelException) {
+ } catch (e: TestCompleted) {
check(env.isCompleted) // expected when test was completed
return
}
@@ -107,4 +110,6 @@
val bits = receivedBits.get(index)
return bits and mask != 0L
}
+
+ private class TestCompleted : CancellationException()
}
diff --git a/kotlinx-coroutines-core/jvm/test/linearizability/ChannelCloseLCStressTest.kt b/kotlinx-coroutines-core/jvm/test/linearizability/ChannelCloseLCStressTest.kt
new file mode 100644
index 0000000..5bdc284
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/linearizability/ChannelCloseLCStressTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:Suppress("unused")
+
+package kotlinx.coroutines.linearizability
+
+import com.devexperts.dxlab.lincheck.*
+import com.devexperts.dxlab.lincheck.annotations.*
+import com.devexperts.dxlab.lincheck.paramgen.*
+import com.devexperts.dxlab.lincheck.strategy.stress.*
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.*
+import org.junit.*
+import java.io.*
+
+/**
+ * This is stress test that is fine-tuned to catch the problem
+ * [#1419](https://github.com/Kotlin/kotlinx.coroutines/issues/1419)
+ */
+@Param(name = "value", gen = IntGen::class, conf = "2:2")
+@OpGroupConfig.OpGroupConfigs(
+ OpGroupConfig(name = "send", nonParallel = true),
+ OpGroupConfig(name = "receive", nonParallel = true),
+ OpGroupConfig(name = "close", nonParallel = true)
+)
+class ChannelCloseLCStressTest : TestBase() {
+
+ private companion object {
+ // Emulating ctor argument for lincheck
+ var capacity = 0
+ }
+
+ private val lt = LinTesting()
+ private var channel: Channel<Int> = Channel(capacity)
+
+ @Operation(runOnce = true, group = "send")
+ fun send1(@Param(name = "value") value: Int) = lt.run("send1") { channel.send(value) }
+
+ @Operation(runOnce = true, group = "send")
+ fun send2(@Param(name = "value") value: Int) = lt.run("send2") { channel.send(value) }
+
+ @Operation(runOnce = true, group = "receive")
+ fun receive1() = lt.run("receive1") { channel.receive() }
+
+ @Operation(runOnce = true, group = "receive")
+ fun receive2() = lt.run("receive2") { channel.receive() }
+
+ @Operation(runOnce = true, group = "close")
+ fun close1() = lt.run("close1") { channel.close(IOException("close1")) }
+
+ @Operation(runOnce = true, group = "close")
+ fun close2() = lt.run("close2") { channel.close(IOException("close2")) }
+
+ @Test
+ fun testRendezvousChannelLinearizability() {
+ runTest(0)
+ }
+
+ @Test
+ fun testArrayChannelLinearizability() {
+ for (i in listOf(1, 2, 16)) {
+ runTest(i)
+ }
+ }
+
+ @Test
+ fun testConflatedChannelLinearizability() = runTest(Channel.CONFLATED)
+
+ @Test
+ fun testUnlimitedChannelLinearizability() = runTest(Channel.UNLIMITED)
+
+ private fun runTest(capacity: Int) {
+ ChannelCloseLCStressTest.capacity = capacity
+ val options = StressOptions()
+ .iterations(1) // only one iteration -- test scenario is fixed
+ .invocationsPerIteration(10_000 * stressTestMultiplierSqrt)
+ .threads(3)
+ .verifier(LinVerifier::class.java)
+ LinChecker.check(ChannelCloseLCStressTest::class.java, options)
+ }
+}
diff --git a/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt b/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt
index ec72701..fa73534 100644
--- a/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt
+++ b/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.debug
@@ -11,6 +11,7 @@
class CoroutinesDumpTest : DebugTestBase() {
private val monitor = Any()
+ private var coroutineStarted = false // guarded by monitor
@Test
fun testSuspendedCoroutine() = synchronized(monitor) {
@@ -130,7 +131,7 @@
private suspend fun nestedActiveMethod(shouldSuspend: Boolean) {
if (shouldSuspend) yield()
- notifyTest()
+ notifyCoroutineStarted()
while (coroutineContext[Job]!!.isActive) {
Thread.sleep(100)
}
@@ -143,17 +144,18 @@
private suspend fun sleepingNestedMethod() {
yield()
- notifyTest()
+ notifyCoroutineStarted()
delay(Long.MAX_VALUE)
}
private fun awaitCoroutineStarted() {
- (monitor as Object).wait()
+ while (!coroutineStarted) (monitor as Object).wait()
}
- private fun notifyTest() {
+ private fun notifyCoroutineStarted() {
synchronized(monitor) {
- (monitor as Object).notify()
+ coroutineStarted = true
+ (monitor as Object).notifyAll()
}
}
}
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/build.gradle b/ui/kotlinx-coroutines-android/animation-app/app/build.gradle
index b5919be..7fd9c8a 100644
--- a/ui/kotlinx-coroutines-android/animation-app/app/build.gradle
+++ b/ui/kotlinx-coroutines-android/animation-app/app/build.gradle
@@ -1,35 +1,33 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
- compileSdkVersion 27
+ compileSdkVersion 29
defaultConfig {
applicationId "org.jetbrains.kotlinx.animation"
minSdkVersion 14
- targetSdkVersion 27
+ targetSdkVersion 29
versionCode 1
versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
- implementation 'com.android.support:appcompat-v7:27.1.1'
- implementation 'com.android.support.constraint:constraint-layout:1.0.2'
- implementation 'com.android.support:design:27.1.1'
- implementation 'android.arch.lifecycle:extensions:1.1.1'
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'com.google.android.material:material:1.0.0'
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
testImplementation 'junit:junit:4.12'
- androidTestImplementation 'com.android.support.test:runner:1.0.1'
- androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
+ androidTestImplementation 'androidx.test:runner:1.2.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/proguard-rules.pro b/ui/kotlinx-coroutines-android/animation-app/app/proguard-rules.pro
deleted file mode 100644
index aea920a..0000000
--- a/ui/kotlinx-coroutines-android/animation-app/app/proguard-rules.pro
+++ /dev/null
@@ -1,8 +0,0 @@
--keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
--keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
--keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {}
--keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {}
-
--keepclassmembernames class kotlinx.** {
- volatile <fields>;
-}
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/Animation.kt b/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/Animation.kt
index dd4aafb..88e0bae 100644
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/Animation.kt
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/Animation.kt
@@ -1,9 +1,13 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
package org.jetbrains.kotlinx.animation
-import android.arch.lifecycle.LifecycleOwner
-import android.arch.lifecycle.MutableLiveData
-import android.arch.lifecycle.Observer
-import android.arch.lifecycle.ViewModel
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import androidx.lifecycle.ViewModel
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/MainActivity.kt b/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/MainActivity.kt
index 87a857c..756db9b 100644
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/MainActivity.kt
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/java/org/jetbrains/kotlinx/animation/MainActivity.kt
@@ -1,8 +1,12 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
package org.jetbrains.kotlinx.animation
-import android.arch.lifecycle.ViewModelProviders
+import androidx.lifecycle.ViewModelProviders
import android.os.Bundle
-import android.support.v7.app.AppCompatActivity
+import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.content_main.*
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/activity_main.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/activity_main.xml
index cfc022f..ad11f2a 100644
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/activity_main.xml
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/activity_main.xml
@@ -1,28 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
-<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<!--
+ ~ Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ -->
+
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.jetbrains.kotlinx.animation.MainActivity">
- <android.support.design.widget.AppBarLayout
+ <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
- <android.support.v7.widget.Toolbar
+ <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
- </android.support.design.widget.AppBarLayout>
+ </com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_main" />
- <android.support.design.widget.FloatingActionButton
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/addButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -31,7 +35,7 @@
app:backgroundTint="@color/colorPrimary"
app:srcCompat="@android:drawable/ic_input_add" />
- <android.support.design.widget.FloatingActionButton
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/removeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -40,4 +44,4 @@
app:backgroundTint="@color/colorPrimary"
app:srcCompat="@android:drawable/ic_delete" />
-</android.support.design.widget.CoordinatorLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/content_main.xml b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/content_main.xml
index 02058bd..30665fe 100644
--- a/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/content_main.xml
+++ b/ui/kotlinx-coroutines-android/animation-app/app/src/main/res/layout/content_main.xml
@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<!--
+ ~ Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@@ -17,4 +21,4 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
-</android.support.constraint.ConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/ui/kotlinx-coroutines-android/animation-app/build.gradle b/ui/kotlinx-coroutines-android/animation-app/build.gradle
index f512a87..d198b1a 100644
--- a/ui/kotlinx-coroutines-android/animation-app/build.gradle
+++ b/ui/kotlinx-coroutines-android/animation-app/build.gradle
@@ -1,3 +1,7 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
@@ -6,7 +10,7 @@
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.4.1'
+ classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/ui/kotlinx-coroutines-android/animation-app/gradle.properties b/ui/kotlinx-coroutines-android/animation-app/gradle.properties
index 8e119d7..19a1481 100644
--- a/ui/kotlinx-coroutines-android/animation-app/gradle.properties
+++ b/ui/kotlinx-coroutines-android/animation-app/gradle.properties
@@ -1,3 +1,7 @@
+#
+# Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
@@ -16,8 +20,9 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
-kotlin.coroutines=enable
-
kotlin_version=1.3.50
coroutines_version=1.3.1
+android.useAndroidX=true
+android.enableJetifier=true
+
diff --git a/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties b/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties
index caf54fa..ab5d60b 100644
--- a/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties
+++ b/ui/kotlinx-coroutines-android/animation-app/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,9 @@
+#
+# Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/ui/kotlinx-coroutines-android/example-app/app/build.gradle b/ui/kotlinx-coroutines-android/example-app/app/build.gradle
index 98257d3..f970baa 100644
--- a/ui/kotlinx-coroutines-android/example-app/app/build.gradle
+++ b/ui/kotlinx-coroutines-android/example-app/app/build.gradle
@@ -1,34 +1,32 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
- compileSdkVersion 27
+ compileSdkVersion 29
defaultConfig {
applicationId "com.example.app"
minSdkVersion 14
- targetSdkVersion 27
+ targetSdkVersion 29
versionCode 1
versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
}
dependencies {
- implementation 'com.android.support:appcompat-v7:27.1.1'
- implementation 'com.android.support.constraint:constraint-layout:1.0.2'
- implementation 'com.android.support:design:27.1.1'
+ implementation 'androidx.appcompat:appcompat:1.0.2'
+ implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'com.google.android.material:material:1.0.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
testImplementation 'junit:junit:4.12'
- androidTestImplementation 'com.android.support.test:runner:1.0.1'
- androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
+ androidTestImplementation 'androidx.test:runner:1.2.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
diff --git a/ui/kotlinx-coroutines-android/example-app/app/proguard-rules.pro b/ui/kotlinx-coroutines-android/example-app/app/proguard-rules.pro
deleted file mode 100644
index aea920a..0000000
--- a/ui/kotlinx-coroutines-android/example-app/app/proguard-rules.pro
+++ /dev/null
@@ -1,8 +0,0 @@
--keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
--keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
--keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {}
--keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {}
-
--keepclassmembernames class kotlinx.** {
- volatile <fields>;
-}
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt b/ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt
index fc1cdbf..47bd16c 100644
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/java/com/example/app/MainActivity.kt
@@ -1,8 +1,12 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
package com.example.app
import android.os.Bundle
-import android.support.design.widget.FloatingActionButton
-import android.support.v7.app.AppCompatActivity
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import android.widget.TextView
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/activity_main.xml b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/activity_main.xml
index 13d3225..b98ce43 100644
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/activity_main.xml
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/activity_main.xml
@@ -1,28 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
-<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<!--
+ ~ Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ -->
+
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.app.MainActivity">
- <android.support.design.widget.AppBarLayout
+ <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
- <android.support.v7.widget.Toolbar
+ <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
- </android.support.design.widget.AppBarLayout>
+ </com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_main" />
- <android.support.design.widget.FloatingActionButton
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -30,4 +34,4 @@
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_dialog_email" />
-</android.support.design.widget.CoordinatorLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/content_main.xml b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/content_main.xml
index 110dc67..6eb08c0 100644
--- a/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/content_main.xml
+++ b/ui/kotlinx-coroutines-android/example-app/app/src/main/res/layout/content_main.xml
@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<!--
+ ~ Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
@@ -18,4 +22,4 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
-</android.support.constraint.ConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/ui/kotlinx-coroutines-android/example-app/build.gradle b/ui/kotlinx-coroutines-android/example-app/build.gradle
index f512a87..d198b1a 100644
--- a/ui/kotlinx-coroutines-android/example-app/build.gradle
+++ b/ui/kotlinx-coroutines-android/example-app/build.gradle
@@ -1,3 +1,7 @@
+/*
+ * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
@@ -6,7 +10,7 @@
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.4.1'
+ classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/ui/kotlinx-coroutines-android/example-app/gradle.properties b/ui/kotlinx-coroutines-android/example-app/gradle.properties
index 8e119d7..19a1481 100644
--- a/ui/kotlinx-coroutines-android/example-app/gradle.properties
+++ b/ui/kotlinx-coroutines-android/example-app/gradle.properties
@@ -1,3 +1,7 @@
+#
+# Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
@@ -16,8 +20,9 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
-kotlin.coroutines=enable
-
kotlin_version=1.3.50
coroutines_version=1.3.1
+android.useAndroidX=true
+android.enableJetifier=true
+
diff --git a/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties b/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties
index caf54fa..ab5d60b 100644
--- a/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties
+++ b/ui/kotlinx-coroutines-android/example-app/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,9 @@
+#
+# Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+#
+
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip