commonMain.channels.Channels.common.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlinx-coroutines-core
Show all versions of kotlinx-coroutines-core
Coroutines support libraries for Kotlin
@file:JvmMultifileClass
@file:JvmName("ChannelsKt")
@file:OptIn(ExperimentalContracts::class)
package kotlinx.coroutines.channels
import kotlinx.coroutines.*
import kotlinx.coroutines.selects.*
import kotlin.contracts.*
import kotlin.jvm.*
internal const val DEFAULT_CLOSE_MESSAGE = "Channel was closed"
// -------- Operations on BroadcastChannel --------
/**
* This function is deprecated in the favour of [ReceiveChannel.receiveCatching].
*
* This function is considered error-prone for the following reasons;
* - Is throwing if the channel has failed even though its signature may suggest it returns 'null'
* - It is easy to forget that exception handling still have to be explicit
* - During code reviews and code reading, intentions of the code are frequently unclear:
* are potential exceptions ignored deliberately or not?
*
* @suppress doc
*/
@Deprecated(
"Deprecated in the favour of 'receiveCatching'",
ReplaceWith("receiveCatching().getOrNull()"),
DeprecationLevel.HIDDEN
) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0
@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "DEPRECATION_ERROR")
public suspend fun ReceiveChannel.receiveOrNull(): E? {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
return (this as ReceiveChannel).receiveOrNull()
}
/**
* This function is deprecated in the favour of [ReceiveChannel.onReceiveCatching]
*/
@Deprecated(
"Deprecated in the favour of 'onReceiveCatching'",
level = DeprecationLevel.HIDDEN
) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0
@Suppress("DEPRECATION_ERROR")
public fun ReceiveChannel.onReceiveOrNull(): SelectClause1 {
return (this as ReceiveChannel).onReceiveOrNull
}
/**
* Executes the [block] and then [cancels][ReceiveChannel.cancel] the channel.
*
* It is guaranteed that, after invoking this operation, the channel will be [cancelled][ReceiveChannel.cancel], so
* the operation is _terminal_.
* If the [block] finishes with an exception, that exception will be used for cancelling the channel and rethrown.
*
* This function is useful for building more complex terminal operators while ensuring that the producers stop sending
* new elements to the channel.
*
* Example:
* ```
* suspend fun ReceiveChannel.consumeFirst(): E =
* consume { return receive() }
* // Launch a coroutine that constantly sends new values
* val channel = produce(Dispatchers.Default) {
* var i = 0
* while (true) {
* // Will fail with a `CancellationException`
* // after `consumeFirst` finishes.
* send(i++)
* }
* }
* // Grab the first value and discard everything else
* val firstElement = channel.consumeFirst()
* check(firstElement == 0)
* // *Note*: some elements could be lost in the channel!
* ```
*
* In this example, the channel will get closed, and the producer coroutine will finish its work after the first
* element is obtained.
* If `consumeFirst` was implemented as `for (e in this) { return e }` instead, the producer coroutine would be active
* until it was cancelled some other way.
*
* [consume] does not guarantee that new elements will not enter the channel after [block] finishes executing, so
* some channel elements may be lost.
* Use the `onUndeliveredElement` parameter of a manually created [Channel] to define what should happen with these
* elements during [ReceiveChannel.cancel].
*/
public inline fun ReceiveChannel.consume(block: ReceiveChannel.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
var cause: Throwable? = null
try {
return block()
} catch (e: Throwable) {
cause = e
throw e
} finally {
cancelConsumed(cause)
}
}
/**
* Performs the given [action] for each received element and [cancels][ReceiveChannel.cancel] the channel afterward.
*
* This function stops processing elements when either the channel is [closed][SendChannel.close],
* the coroutine in which the collection is performed gets cancelled and there are no readily available elements in the
* channel's buffer,
* [action] fails with an exception,
* or an early return from [action] happens.
* If the [action] finishes with an exception, that exception will be used for cancelling the channel and rethrown.
* If the channel is [closed][SendChannel.close] with a cause, this cause will be rethrown from [consumeEach].
*
* When the channel does not need to be closed after iterating over its elements,
* a regular `for` loop (`for (element in channel)`) should be used instead.
*
* The operation is _terminal_.
* This function [consumes][ReceiveChannel.consume] the elements of the original [ReceiveChannel].
*
* This function is useful in cases when this channel is only expected to have a single consumer that decides when
* the producer may stop.
* Example:
*
* ```
* val channel = Channel(1)
* // Launch several procedures that create values
* repeat(5) {
* launch(Dispatchers.Default) {
* while (true) {
* channel.send(Random.nextInt(40, 50))
* }
* }
* }
* // Launch the exclusive consumer
* val result = run {
* channel.consumeEach {
* if (it == 42) {
* println("Found the answer")
* return@run it // forcibly stop collection
* }
* }
* // *Note*: some elements could be lost in the channel!
* }
* check(result == 42)
* ```
*
* In this example, several coroutines put elements into a single channel, and a single consumer processes the elements.
* Once it finds the elements it's looking for, it stops [consumeEach] by making an early return.
*
* **Pitfall**: even though the name says "each", some elements could be left unprocessed if they are added after
* this function decided to close the channel.
* In this case, the elements will simply be lost.
* If the elements of the channel are resources that must be closed (like file handles, sockets, etc.),
* an `onUndeliveredElement` must be passed to the [Channel] on construction.
* It will be called for each element left in the channel at the point of cancellation.
*/
public suspend inline fun ReceiveChannel.consumeEach(action: (E) -> Unit): Unit =
consume {
for (e in this) action(e)
}
/**
* Returns a [List] containing all the elements sent to this channel, preserving their order.
*
* This function will attempt to receive elements and put them into the list until the channel is
* [closed][SendChannel.close].
* Calling [toList] on channels that are not eventually closed is always incorrect:
* - It will suspend indefinitely if the channel is not closed, but no new elements arrive.
* - If new elements do arrive and the channel is not eventually closed, [toList] will use more and more memory
* until exhausting it.
*
* If the channel is [closed][SendChannel.close] with a cause, [toList] will rethrow that cause.
*
* The operation is _terminal_.
* This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
*
* Example:
* ```
* val values = listOf(1, 5, 2, 9, 3, 3, 1)
* // start a new coroutine that creates a channel,
* // sends elements to it, and closes it
* // once the coroutine's body finishes
* val channel = produce {
* values.forEach { send(it) }
* }
* check(channel.toList() == values)
* ```
*/
public suspend fun ReceiveChannel.toList(): List = buildList {
consumeEach {
add(it)
}
}
@PublishedApi
internal fun ReceiveChannel<*>.cancelConsumed(cause: Throwable?) {
cancel(cause?.let {
it as? CancellationException ?: CancellationException("Channel was consumed, consumer had failed", it)
})
}