Skip to content

Commit 25d4311

Browse files
gitpaxultekAleksDanil
authored andcommitted
Make BufferedChannelIterator#hasNext idempotent (#4065)
The rationale in favor of the change includes: * in the `1.6.x` series `Channel().iterator().hasNext()` was idempotent * despite the lack of requirement for the `Iterator#hasNext` method implementations to be idempotent, it is generally safer to make them such. The same applies to `ChannelIterator#hasNext`
1 parent 3b9ab7f commit 25d4311

File tree

2 files changed

+41
-4
lines changed

2 files changed

+41
-4
lines changed

kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,8 +1580,8 @@ internal open class BufferedChannel<E>(
15801580
* [CancellableContinuation] and [SelectInstance].
15811581
*
15821582
* Roughly, [hasNext] is a [receive] sibling, while [next] simply
1583-
* returns the already retrieved element. From the implementation
1584-
* side, [receiveResult] stores the element retrieved by [hasNext]
1583+
* returns the already retrieved element and [hasNext] being idempotent.
1584+
* From the implementation side, [receiveResult] stores the element retrieved by [hasNext]
15851585
* (or a special [CHANNEL_CLOSED] token if the channel is closed).
15861586
*
15871587
* The [invoke] function is a [CancelHandler] implementation,
@@ -1614,8 +1614,10 @@ internal open class BufferedChannel<E>(
16141614
private var continuation: CancellableContinuationImpl<Boolean>? = null
16151615

16161616
// `hasNext()` is just a special receive operation.
1617-
override suspend fun hasNext(): Boolean =
1618-
receiveImpl( // <-- this is an inline function
1617+
override suspend fun hasNext(): Boolean {
1618+
return if (this.receiveResult !== NO_RECEIVE_RESULT && this.receiveResult !== CHANNEL_CLOSED) {
1619+
true
1620+
} else receiveImpl( // <-- this is an inline function
16191621
// Do not create a continuation until it is required;
16201622
// it is created later via [onNoWaiterSuspend], if needed.
16211623
waiter = null,
@@ -1636,6 +1638,7 @@ internal open class BufferedChannel<E>(
16361638
// The tail-call optimization is applied here.
16371639
onNoWaiterSuspend = { segm, i, r -> return hasNextOnNoWaiterSuspend(segm, i, r) }
16381640
)
1641+
}
16391642

16401643
private fun onClosedHasNext(): Boolean {
16411644
this.receiveResult = CHANNEL_CLOSED

kotlinx-coroutines-core/common/test/channels/BufferedChannelTest.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,40 @@ import kotlinx.coroutines.*
55
import kotlin.test.*
66

77
class BufferedChannelTest : TestBase() {
8+
@Test
9+
fun testIteratorHasNextIsIdempotent() = runTest {
10+
val q = Channel<Int>()
11+
check(q.isEmpty)
12+
val iter = q.iterator()
13+
expect(1)
14+
val sender = launch {
15+
expect(4)
16+
q.send(1) // sent
17+
expect(10)
18+
q.close()
19+
expect(11)
20+
}
21+
expect(2)
22+
val receiver = launch {
23+
expect(5)
24+
check(iter.hasNext())
25+
expect(6)
26+
check(iter.hasNext())
27+
expect(7)
28+
check(iter.hasNext())
29+
expect(8)
30+
check(iter.next() == 1)
31+
expect(9)
32+
check(!iter.hasNext())
33+
expect(12)
34+
}
35+
expect(3)
36+
sender.join()
37+
receiver.join()
38+
check(q.isClosedForReceive)
39+
finish(13)
40+
}
41+
842
@Test
943
fun testSimple() = runTest {
1044
val q = Channel<Int>(1)

0 commit comments

Comments
 (0)