Skip to content

Commit 3c6d743

Browse files
committed
Make ChannelIterator.next non-suspending
Fixes #1162
1 parent 725addf commit 3c6d743

File tree

4 files changed

+37
-21
lines changed

4 files changed

+37
-21
lines changed

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt

+6-1
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,12 @@ public final class kotlinx/coroutines/channels/Channel$Factory {
561561

562562
public abstract interface class kotlinx/coroutines/channels/ChannelIterator {
563563
public abstract fun hasNext (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
564-
public abstract fun next (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
564+
public abstract fun next ()Ljava/lang/Object;
565+
public abstract synthetic fun next (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
566+
}
567+
568+
public final class kotlinx/coroutines/channels/ChannelIterator$DefaultImpls {
569+
public static synthetic fun next (Lkotlinx/coroutines/channels/ChannelIterator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
565570
}
566571

567572
public final class kotlinx/coroutines/channels/ChannelKt {

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -901,15 +901,15 @@ internal abstract class AbstractChannel<E> : AbstractSendChannel<E>(), Channel<E
901901
}
902902

903903
@Suppress("UNCHECKED_CAST")
904-
override suspend fun next(): E {
904+
override fun next(): E {
905905
val result = this.result
906906
if (result is Closed<*>) throw recoverStackTrace(result.receiveException)
907907
if (result !== POLL_FAILED) {
908908
this.result = POLL_FAILED
909909
return result as E
910910
}
911-
// rare case when hasNext was not invoked yet -- just delegate to receive (leave state as is)
912-
return channel.receive()
911+
912+
throw IllegalStateException("'hasNext' should be called prior to 'next' invocation")
913913
}
914914
}
915915

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

+16-17
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import kotlinx.coroutines.channels.Channel.Factory.CONFLATED
1111
import kotlinx.coroutines.channels.Channel.Factory.RENDEZVOUS
1212
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
1313
import kotlinx.coroutines.selects.*
14-
import kotlin.jvm.*
14+
import kotlin.coroutines.*
1515

1616
/**
1717
* Sender's interface to [Channel].
@@ -295,25 +295,24 @@ public interface ChannelIterator<out E> {
295295
*/
296296
public suspend operator fun hasNext(): Boolean
297297

298+
@Deprecated(message = "Since 1.3.0, binary compatibility with versions <= 1.2.x", level = DeprecationLevel.HIDDEN)
299+
public fun next(continuation: Continuation<*>): Any? = next()
300+
298301
/**
299-
* Retrieves and removes the element from this channel suspending the caller while this channel
300-
* is empty or throws [ClosedReceiveChannelException] if the channel
301-
* [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause.
302-
* It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
303-
*
304-
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
305-
* function is suspended, this function immediately resumes with [CancellationException].
306-
*
307-
* *Cancellation of suspended receive is atomic* -- when this function
308-
* throws [CancellationException] it means that the element was not retrieved from this channel.
309-
* As a side-effect of atomic cancellation, a thread-bound coroutine (to some UI thread, for example) may
310-
* continue to execute even after it was cancelled from the same thread in the case when this receive operation
311-
* was already resumed and the continuation was posted for execution to the thread's queue.
302+
* Retrieves the element from the current iterator previously removed from the channel by preceding call to [hasNext] or
303+
* throws [IllegalStateException] if [hasNext] was not invoked.
304+
* [next] should only be used in pair with [hasNext]:
305+
* ```
306+
* while (iterator.hasNext()) {
307+
* val element = iterator.next()
308+
* // ... handle element ...
309+
* }
310+
* ```
312311
*
313-
* Note that this function does not check for cancellation when it is not suspended.
314-
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
312+
* This method throws [ClosedReceiveChannelException] if the channel [isClosedForReceive][ReceiveChannel.isClosedForReceive] without cause.
313+
* It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
315314
*/
316-
public suspend operator fun next(): E
315+
public operator fun next(): E
317316
}
318317

319318
/**

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

+12
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ class BasicOperationsTest : TestBase() {
7373
finish(4)
7474
}
7575

76+
@Test
77+
fun testIterator() = runTest {
78+
TestChannelKind.values().forEach { kind ->
79+
val channel = kind.create()
80+
val iterator = channel.iterator()
81+
assertFailsWith<IllegalStateException> { iterator.next() }
82+
channel.close()
83+
assertFailsWith<IllegalStateException> { iterator.next() }
84+
assertFalse(iterator.hasNext())
85+
}
86+
}
87+
7688
private suspend fun testReceiveOrNull(kind: TestChannelKind) = coroutineScope {
7789
val channel = kind.create()
7890
val d = async(NonCancellable) {

0 commit comments

Comments
 (0)