diff --git a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt index fb6846ef4e..0d63050357 100644 --- a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt @@ -1580,8 +1580,8 @@ internal open class BufferedChannel( * [CancellableContinuation] and [SelectInstance]. * * Roughly, [hasNext] is a [receive] sibling, while [next] simply - * returns the already retrieved element. From the implementation - * side, [receiveResult] stores the element retrieved by [hasNext] + * returns the already retrieved element and [hasNext] being idempotent. + * From the implementation side, [receiveResult] stores the element retrieved by [hasNext] * (or a special [CHANNEL_CLOSED] token if the channel is closed). * * The [invoke] function is a [CancelHandler] implementation, @@ -1614,8 +1614,10 @@ internal open class BufferedChannel( private var continuation: CancellableContinuationImpl? = null // `hasNext()` is just a special receive operation. - override suspend fun hasNext(): Boolean = - receiveImpl( // <-- this is an inline function + override suspend fun hasNext(): Boolean { + return if (this.receiveResult !== NO_RECEIVE_RESULT && this.receiveResult !== CHANNEL_CLOSED) { + true + } else receiveImpl( // <-- this is an inline function // Do not create a continuation until it is required; // it is created later via [onNoWaiterSuspend], if needed. waiter = null, @@ -1636,6 +1638,7 @@ internal open class BufferedChannel( // The tail-call optimization is applied here. onNoWaiterSuspend = { segm, i, r -> return hasNextOnNoWaiterSuspend(segm, i, r) } ) + } private fun onClosedHasNext(): Boolean { this.receiveResult = CHANNEL_CLOSED diff --git a/kotlinx-coroutines-core/common/test/channels/BufferedChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/BufferedChannelTest.kt index 27ac0057f4..39b9f9d755 100644 --- a/kotlinx-coroutines-core/common/test/channels/BufferedChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/BufferedChannelTest.kt @@ -5,6 +5,40 @@ import kotlinx.coroutines.* import kotlin.test.* class BufferedChannelTest : TestBase() { + @Test + fun testIteratorHasNextIsIdempotent() = runTest { + val q = Channel() + check(q.isEmpty) + val iter = q.iterator() + expect(1) + val sender = launch { + expect(4) + q.send(1) // sent + expect(10) + q.close() + expect(11) + } + expect(2) + val receiver = launch { + expect(5) + check(iter.hasNext()) + expect(6) + check(iter.hasNext()) + expect(7) + check(iter.hasNext()) + expect(8) + check(iter.next() == 1) + expect(9) + check(!iter.hasNext()) + expect(12) + } + expect(3) + sender.join() + receiver.join() + check(q.isClosedForReceive) + finish(13) + } + @Test fun testSimple() = runTest { val q = Channel(1)