Skip to content

Conversation

@dkhalanskyjb
Copy link
Collaborator

Use case: collecting elements up until the point the channel is closed without losing the elements when toList when the exception is thrown.

This function is similar to Flow<T>.toList(destination), which we already have, so the addition makes sense from the point of view of consistency as well.

Use case: collecting elements up until the point the channel is
closed without losing the elements when `toList` when the exception
is thrown.

This function is similar to `Flow<T>.toList(destination)`,
which we already have, so the addition makes sense from the
point of view of consistency as well.
@dkhalanskyjb dkhalanskyjb requested a review from murfel September 10, 2025 08:16
Copy link
Contributor

@murfel murfel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkhalanskyjb dkhalanskyjb force-pushed the dkhalanskyjb/Channel.toList(destination) branch from 63b1e1a to 2d8f52b Compare September 10, 2025 10:49
@dkhalanskyjb dkhalanskyjb force-pushed the dkhalanskyjb/Channel.toList(destination) branch from 4992f06 to aa1adf1 Compare September 10, 2025 11:04
@dkhalanskyjb dkhalanskyjb force-pushed the dkhalanskyjb/Channel.toList(destination) branch from aa1adf1 to 6039cf8 Compare September 10, 2025 11:05
@murfel murfel self-requested a review October 9, 2025 09:43
@murfel
Copy link
Contributor

murfel commented Oct 9, 2025

Could you also fix the Channel.toList docs to avoid saying "all elements" (two entries on this page)
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/to-list.html

@dkhalanskyjb
Copy link
Collaborator Author

dkhalanskyjb commented Oct 14, 2025

@murfel, that page is automatically generated from the documentation comments that are edited in this PR.

@murfel
Copy link
Contributor

murfel commented Oct 14, 2025

I'm aware.

@dkhalanskyjb dkhalanskyjb force-pushed the dkhalanskyjb/Channel.toList(destination) branch from 938369e to caed24d Compare October 14, 2025 10:41
*
* This function will attempt to receive elements and put them into the list until the channel is
* This is a convenience function equivalent to calling [consumeAsFlow] followed by [kotlinx.coroutines.flow.toList].
* It is useful for testing code that uses channels to observe the elements the channel contains at the end of the test.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* It is useful for testing code that uses channels to observe the elements the channel contains at the end of the test.
* It is useful for testing to observe the elements the channel contains at the end of the test.

*
* There is no way to recover channel elements if the channel gets closed with an exception
* or to apply additional transformations to the elements before building the resulting collection.
* Please use [consumeAsFlow] and [kotlinx.coroutines.flow.toCollection] for such advanced use-cases.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Please use [consumeAsFlow] and [kotlinx.coroutines.flow.toCollection] for such advanced use-cases.
* Please use either [consumeTo] or [consumeAsFlow] and [kotlinx.coroutines.flow.toCollection] for such advanced use-cases.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do want to direct the users to Flow here.

* check(channel.toList() == values)
* ```
*/
public suspend fun <E> ReceiveChannel<E>.toList(): List<E> = buildList {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We forgot about lost elements (onUndeliveredElement). It should be at least mentioned in the doc, and possibly onUndeliveredElement could be added as a parameter.

onUndeliveredElement overcomplicates the signature for no real use-case, but provides completeness. No strong opinion on my end.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain what behavior of onUndeliveredElement is missing from the description? We do say that the elements that did get delivered may still be lost if the channel is closed with a cause.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, found it.

It should be placed (or duplicated) at the end, near the description of its terminal behaviour, since it's logically connected.

* [consumeTo] attempts to receive elements and put them into the collection until the channel is
* [closed][SendChannel.close].
*
* If the channel is [closed][SendChannel.close] with a cause, [consumeTo] will rethrow that cause.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* If the channel is [closed][SendChannel.close] with a cause, [consumeTo] will rethrow that cause.
* If the channel is [closed][SendChannel.close] with a cause, [consumeTo] will rethrow that cause immediately.
* The channel could still contain elements that will never be received.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I short circuited (and also misread the docs). Please do add when exactly the exception will be rethrown, though.

The channel could still contain elements that will never be received.

Elaborate this whole scenario.

* In practice, this means that if adding new elements to the collection fails with an exception,
* that exception will be used for [cancelling][ReceiveChannel.cancel] the channel and rethrown.
*
* The intended use case for this function is collecting the remaining elements of a closed channel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily "the remaining elements of a closed channel".

It is definitely for use when we know that the channel is or will be closed at some point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your sample is of this use-case, but there are others.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you're describing is not the intended use case. If you only know the channel will be closed at some point, then consumeTo uses an unbound amount of memory, which we try to avoid. A possible use case is sending a bounded number of elements (for example, we know there will be at most 100 elements before the channel gets closed). I'd argue that channels are not the right tool for the job in that case, you should instead build a list on the sender's side and publish it as a whole to the receiver.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have a fairly closed scope for channels in mind, could you elaborate such use-cases in the kdoc? Currently it leaves me puzzled as to why this is the only case presented, even though I can see that I can use it in other ways.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's a good idea to mention Flow (which take over most of the typical use cases for Channel) in the Channel documentation: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-channel/

Certainly out of scope of this PR, though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but I still can't verify if that's the only reasonable use-case worth mentioning.

* channel.consumeTo(remainingElements)
* } finally {
* println("Remaining elements: $remainingElements")
* }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A more closer example would be to have a channel which is can be closed with a cause, to separate the usage of this function from just channel.toList()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also would be nice to show an example when consumeTo is used before the channel is definitely closed.

(This example could approach this by just removing delay on L251)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit wary that you are advocating exclusively for one particular use-case, here in the sample and in the kdoc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's discuss the intention of this API and samples on Monday!

Comment on lines 245 to 251
* while (Random.nextInt() % 100 != 42) {
* while (Random.nextInt() % 100 != 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to teach users to use even adjacent things properly. Random.nextInt(100) requires less random bits per invokation and is also genuinely uniformly distributed.

* check(channel.toList() == values)
* ```
*/
public suspend fun <E> ReceiveChannel<E>.toList(): List<E> = buildList {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, found it.

It should be placed (or duplicated) at the end, near the description of its terminal behaviour, since it's logically connected.

* [consumeTo] attempts to receive elements and put them into the collection until the channel is
* [closed][SendChannel.close].
*
* If the channel is [closed][SendChannel.close] with a cause, [consumeTo] will rethrow that cause.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I short circuited (and also misread the docs). Please do add when exactly the exception will be rethrown, though.

The channel could still contain elements that will never be received.

Elaborate this whole scenario.

* In practice, this means that if adding new elements to the collection fails with an exception,
* that exception will be used for [cancelling][ReceiveChannel.cancel] the channel and rethrown.
*
* The intended use case for this function is collecting the remaining elements of a closed channel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have a fairly closed scope for channels in mind, could you elaborate such use-cases in the kdoc? Currently it leaves me puzzled as to why this is the only case presented, even though I can see that I can use it in other ways.

@murfel
Copy link
Contributor

murfel commented Oct 28, 2025

Each consume-like function has a very similar description. Could you unify these descriptions? Since they are saying the same thing with different words. Or worse, give slightly different information.

E.g. consume and consumeEach both end with

consume

 * [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].

consumeEach

 * **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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants