-
Couldn't load subscription status.
- Fork 1.9k
Add ReceiveChannel.toList(destination) #4520
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For context, Flow's toList(destination) that you've referenced
63b1e1a to
2d8f52b
Compare
4992f06 to
aa1adf1
Compare
aa1adf1 to
6039cf8
Compare
|
Could you also fix the |
|
@murfel, that page is automatically generated from the documentation comments that are edited in this PR. |
|
I'm aware. |
938369e to
caed24d
Compare
| * | ||
| * 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * 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. |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not true: https://pl.kotl.in/U4TKRZ-I2
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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") | ||
| * } |
There was a problem hiding this comment.
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()
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
| * while (Random.nextInt() % 100 != 42) { | ||
| * while (Random.nextInt() % 100 != 0) { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
|
Each E.g.
|
Use case: collecting elements up until the point the channel is closed without losing the elements when
toListwhen 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.