Skip to content

Commit de18150

Browse files
committed
Deprecate ReceiveChannel.poll and replace its usages along the code base
1 parent dedd448 commit de18150

12 files changed

+104
-113
lines changed

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

+73-59
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ public interface SendChannel<in E> {
150150
level = DeprecationLevel.WARNING,
151151
message = "Deprecated in the favour of 'trySend' method",
152152
replaceWith = ReplaceWith("trySend(element).isSuccess")
153-
)
153+
) // Since 1.5.0
154154
public fun offer(element: E): Boolean {
155155
val result = trySend(element)
156156
if (result.isSuccess) return true
@@ -198,7 +198,7 @@ public interface ReceiveChannel<out E> {
198198
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
199199
*
200200
* This function can be used in [select] invocations with the [onReceive] clause.
201-
* Use [poll] to try receiving from this channel without waiting.
201+
* Use [tryReceive] to try receiving from this channel without waiting.
202202
*/
203203
public suspend fun receive(): E
204204

@@ -210,51 +210,6 @@ public interface ReceiveChannel<out E> {
210210
*/
211211
public val onReceive: SelectClause1<E>
212212

213-
/**
214-
* This function was deprecated since 1.3.0 and is no longer recommended to use
215-
* or to implement in subclasses.
216-
*
217-
* It had the following pitfalls:
218-
* - Didn't allow to distinguish 'null' as "closed channel" from "null as a value"
219-
* - Was throwing if the channel has failed even though its signature may suggest it returns 'null'
220-
* - It didn't really belong to core channel API and can be exposed as an extension instead.
221-
*
222-
* @suppress doc
223-
*/
224-
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
225-
@LowPriorityInOverloadResolution
226-
@Deprecated(
227-
message = "Deprecated in favor of receiveCatching",
228-
level = DeprecationLevel.ERROR,
229-
replaceWith = ReplaceWith("receiveCatching().getOrNull()")
230-
) // Warning since 1.3.0, error in 1.5.0, will be hidden in 1.6.0
231-
public suspend fun receiveOrNull(): E? = receiveCatching().getOrNull()
232-
233-
/**
234-
* This function was deprecated since 1.3.0 and is no longer recommended to use
235-
* or to implement in subclasses.
236-
* See [receiveOrNull] documentation.
237-
*
238-
* @suppress **Deprecated**: in favor of onReceiveCatching extension.
239-
*/
240-
@Deprecated(
241-
message = "Deprecated in favor of onReceiveCatching extension",
242-
level = DeprecationLevel.ERROR,
243-
replaceWith = ReplaceWith("onReceiveCatching")
244-
) // Warning since 1.3.0, error in 1.5.0, will be hidden or removed in 1.6.0
245-
public val onReceiveOrNull: SelectClause1<E?>
246-
get() {
247-
return object : SelectClause1<E?> {
248-
@InternalCoroutinesApi
249-
override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (E?) -> R) {
250-
onReceiveCatching.registerSelectClause1(select) {
251-
it.exceptionOrNull()?.let { throw it }
252-
block(it.getOrNull())
253-
}
254-
}
255-
}
256-
}
257-
258213
/**
259214
* Retrieves and removes an element from this channel if it's not empty, or suspends the caller while this channel is empty.
260215
* This method returns [ChannelResult] with the value of an element successfully retrieved from the channel
@@ -272,7 +227,7 @@ public interface ReceiveChannel<out E> {
272227
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
273228
*
274229
* This function can be used in [select] invocations with the [onReceiveCatching] clause.
275-
* Use [poll] to try receiving from this channel without waiting.
230+
* Use [tryReceive] to try receiving from this channel without waiting.
276231
*/
277232
public suspend fun receiveCatching(): ChannelResult<E>
278233

@@ -283,17 +238,6 @@ public interface ReceiveChannel<out E> {
283238
*/
284239
public val onReceiveCatching: SelectClause1<ChannelResult<E>>
285240

286-
/**
287-
* Retrieves and removes an element from this channel if it's not empty or returns `null` if the channel is empty
288-
* or is [is closed for `receive`][isClosedForReceive] without a cause.
289-
* It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
290-
*/
291-
public fun poll(): E? {
292-
val result = tryReceive()
293-
if (result.isSuccess) return result.getOrThrow()
294-
throw recoverStackTrace(result.exceptionOrNull() ?: return null)
295-
}
296-
297241
/**
298242
* Retrieves and removes an element from this channel if it's not empty, returning a [successful][ChannelResult.success]
299243
* result, returns [failed][ChannelResult.failed] result if the channel is empty, and [closed][ChannelResult.closed]
@@ -335,6 +279,76 @@ public interface ReceiveChannel<out E> {
335279
*/
336280
@Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x")
337281
public fun cancel(cause: Throwable? = null): Boolean
282+
283+
284+
/**
285+
* **Deprecated** poll method.
286+
*
287+
* This method was deprecated in the favour of [tryReceive].
288+
* It has proven itself as error-prone method in Channel API:
289+
*
290+
* * Nullable return type creates the false sense of security, implying that `null`
291+
* is returned instead of throwing an exception.
292+
* * It was used mostly from non-suspending APIs where CancellationException triggered
293+
* internal failures in the application (the most common source of bugs).
294+
* * Its name was not aligned with the rest of the API and tried to mimic Java's queue instead.
295+
*
296+
* See https://github.com/Kotlin/kotlinx.coroutines/issues/974 for more context.
297+
*/
298+
@Deprecated(level = DeprecationLevel.WARNING,
299+
message = "Deprecated in the favour of 'tryReceive'",
300+
replaceWith = ReplaceWith("tryReceive().getOrNull()")
301+
) // Since 1.5.0
302+
public fun poll(): E? {
303+
val result = tryReceive()
304+
if (result.isSuccess) return result.getOrThrow()
305+
throw recoverStackTrace(result.exceptionOrNull() ?: return null)
306+
}
307+
308+
/**
309+
* This function was deprecated since 1.3.0 and is no longer recommended to use
310+
* or to implement in subclasses.
311+
*
312+
* It had the following pitfalls:
313+
* - Didn't allow to distinguish 'null' as "closed channel" from "null as a value"
314+
* - Was throwing if the channel has failed even though its signature may suggest it returns 'null'
315+
* - It didn't really belong to core channel API and can be exposed as an extension instead.
316+
*
317+
* @suppress doc
318+
*/
319+
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
320+
@LowPriorityInOverloadResolution
321+
@Deprecated(
322+
message = "Deprecated in favor of receiveCatching",
323+
level = DeprecationLevel.ERROR,
324+
replaceWith = ReplaceWith("receiveCatching().getOrNull()")
325+
) // Warning since 1.3.0, error in 1.5.0, will be hidden in 1.6.0
326+
public suspend fun receiveOrNull(): E? = receiveCatching().getOrNull()
327+
328+
/**
329+
* This function was deprecated since 1.3.0 and is no longer recommended to use
330+
* or to implement in subclasses.
331+
* See [receiveOrNull] documentation.
332+
*
333+
* @suppress **Deprecated**: in favor of onReceiveCatching extension.
334+
*/
335+
@Deprecated(
336+
message = "Deprecated in favor of onReceiveCatching extension",
337+
level = DeprecationLevel.ERROR,
338+
replaceWith = ReplaceWith("onReceiveCatching")
339+
) // Warning since 1.3.0, error in 1.5.0, will be hidden or removed in 1.6.0
340+
public val onReceiveOrNull: SelectClause1<E?>
341+
get() {
342+
return object : SelectClause1<E?> {
343+
@InternalCoroutinesApi
344+
override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (E?) -> R) {
345+
onReceiveCatching.registerSelectClause1(select) {
346+
it.exceptionOrNull()?.let { throw it }
347+
block(it.getOrNull())
348+
}
349+
}
350+
}
351+
}
338352
}
339353

340354
/**

kotlinx-coroutines-core/common/src/flow/internal/Combine.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ internal suspend fun <R, T> FlowCollector<R>.combineInternal(
6565
// Received the second value from the same flow in the same epoch -- bail out
6666
if (lastReceivedEpoch[index] == currentEpoch) break
6767
lastReceivedEpoch[index] = currentEpoch
68-
element = resultChannel.poll() ?: break
68+
element = resultChannel.tryReceive().getOrNull() ?: break
6969
}
7070

7171
// Process batch result if there is enough data

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,15 @@ class ArrayChannelTest : TestBase() {
9292
expect(1)
9393
launch {
9494
expect(3)
95-
assertEquals(1, q.poll())
95+
assertEquals(1, q.tryReceive().getOrNull())
9696
expect(4)
97-
assertNull(q.poll())
97+
assertNull(q.tryReceive().getOrNull())
9898
expect(5)
9999
assertEquals(2, q.receive()) // suspends
100100
expect(9)
101-
assertEquals(3, q.poll())
101+
assertEquals(3, q.tryReceive().getOrNull())
102102
expect(10)
103-
assertNull(q.poll())
103+
assertNull(q.tryReceive().getOrNull())
104104
expect(11)
105105
}
106106
expect(2)

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

-22
Original file line numberDiff line numberDiff line change
@@ -80,28 +80,6 @@ class BasicOperationsTest : TestBase() {
8080
}
8181
}
8282

83-
private suspend fun testReceiveCatchingException(kind: TestChannelKind) = coroutineScope {
84-
val channel = kind.create<Int>()
85-
val d = async(NonCancellable) {
86-
channel.receive()
87-
}
88-
89-
yield()
90-
channel.close(TestException())
91-
assertTrue(channel.isClosedForReceive)
92-
93-
assertFailsWith<TestException> { channel.poll() }
94-
try {
95-
channel.receiveCatching().getOrThrow()
96-
expectUnreached()
97-
} catch (e: TestException) {
98-
// Expected
99-
}
100-
101-
d.join()
102-
assertTrue(d.getCancellationException().cause is TestException)
103-
}
104-
10583
@Suppress("ReplaceAssertBooleanWithAssertEquality")
10684
private suspend fun testReceiveCatching(kind: TestChannelKind) = coroutineScope {
10785
reset()

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class ChannelBufferOverflowTest : TestBase() {
2020
assertTrue(c.trySend(6).isSuccess) // overflows, dropped
2121
assertEquals(2, c.receive())
2222
assertEquals(5, c.receive())
23-
assertEquals(null, c.poll())
23+
assertEquals(null, c.tryReceive().getOrNull())
2424
}
2525

2626
@Test
@@ -35,6 +35,6 @@ class ChannelBufferOverflowTest : TestBase() {
3535
assertTrue(c.trySend(6).isSuccess) // overflows, keeps 5, 6
3636
assertEquals(5, c.receive())
3737
assertEquals(6, c.receive())
38-
assertEquals(null, c.poll())
38+
assertEquals(null, c.tryReceive().getOrNull())
3939
}
4040
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class ConflatedBroadcastChannelTest : TestBase() {
4242
launch(start = CoroutineStart.UNDISPATCHED) {
4343
expect(2)
4444
val sub = broadcast.openSubscription()
45-
assertNull(sub.poll())
45+
assertNull(sub.tryReceive().getOrNull())
4646
expect(3)
4747
assertEquals("one", sub.receive()) // suspends
4848
expect(6)

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ open class ConflatedChannelTest : TestBase() {
1212
Channel<T>(Channel.CONFLATED)
1313

1414
@Test
15-
fun testBasicConflationOfferPoll() {
15+
fun testBasicConflationOfferTryReceive() {
1616
val q = createConflatedChannel<Int>()
17-
assertNull(q.poll())
17+
assertNull(q.tryReceive().getOrNull())
1818
assertTrue(q.trySend(1).isSuccess)
1919
assertTrue(q.trySend(2).isSuccess)
2020
assertTrue(q.trySend(3).isSuccess)
21-
assertEquals(3, q.poll())
22-
assertNull(q.poll())
21+
assertEquals(3, q.tryReceive().getOrNull())
22+
assertNull(q.tryReceive().getOrNull())
2323
}
2424

2525
@Test

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class LinkedListChannelTest : TestBase() {
1717
check(c.close())
1818
check(!c.close())
1919
assertEquals(1, c.receive())
20-
assertEquals(2, c.poll())
20+
assertEquals(2, c.tryReceive().getOrNull())
2121
assertEquals(3, c.receiveCatching().getOrNull())
2222
assertNull(c.receiveCatching().getOrNull())
2323
}

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -80,20 +80,20 @@ class RendezvousChannelTest : TestBase() {
8080
}
8181

8282
@Test
83-
fun testOfferAndPool() = runTest {
83+
fun testTrySendTryReceive() = runTest {
8484
val q = Channel<Int>(Channel.RENDEZVOUS)
8585
assertFalse(q.trySend(1).isSuccess)
8686
expect(1)
8787
launch {
8888
expect(3)
89-
assertNull(q.poll())
89+
assertNull(q.tryReceive().getOrNull())
9090
expect(4)
9191
assertEquals(2, q.receive())
9292
expect(7)
93-
assertNull(q.poll())
93+
assertNull(q.tryReceive().getOrNull())
9494
yield()
9595
expect(9)
96-
assertEquals(3, q.poll())
96+
assertEquals(3, q.tryReceive().getOrNull())
9797
expect(10)
9898
}
9999
expect(2)

kotlinx-coroutines-core/jvm/src/channels/TickerChannels.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ public enum class TickerMode {
2121
* ```
2222
* val channel = ticker(delay = 100)
2323
* delay(350) // 250 ms late
24-
* println(channel.poll()) // prints Unit
25-
* println(channel.poll()) // prints null
24+
* println(channel.tryReceive().getOrNull()) // prints Unit
25+
* println(channel.tryReceive().getOrNull()) // prints null
2626
*
2727
* delay(50)
28-
* println(channel.poll()) // prints Unit, delay was adjusted
28+
* println(channel.tryReceive().getOrNull()) // prints Unit, delay was adjusted
2929
* delay(50)
30-
* println(channel.poll()) // prints null, we'are not late relatively to previous element
30+
* println(channel.tryReceive().getOrNull()) // prints null, we're not late relatively to previous element
3131
* ```
3232
*/
3333
FIXED_PERIOD,

kotlinx-coroutines-core/jvm/test/channels/TickerChannelCommonTest.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class TickerChannelCommonTest(private val channelFactory: Channel) : TestBase()
4848

4949
delayChannel.cancel()
5050
delay(5100)
51-
assertFailsWith<CancellationException> { delayChannel.poll() }
51+
assertFailsWith<CancellationException> { delayChannel.tryReceive().getOrThrow() }
5252
}
5353
}
5454

@@ -159,9 +159,9 @@ class TickerChannelCommonTest(private val channelFactory: Channel) : TestBase()
159159
}
160160
}
161161

162-
fun ReceiveChannel<Unit>.checkEmpty() = assertNull(poll())
162+
fun ReceiveChannel<Unit>.checkEmpty() = assertNull(tryReceive().getOrNull())
163163

164164
fun ReceiveChannel<Unit>.checkNotEmpty() {
165-
assertNotNull(poll())
166-
assertNull(poll())
165+
assertNotNull(tryReceive().getOrNull())
166+
assertNull(tryReceive().getOrNull())
167167
}

kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt

+6-7
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,10 @@ abstract class ChannelLincheckTestBase(
8686
}
8787

8888
@Operation
89-
fun poll(): Any? = try {
90-
c.poll()
91-
} catch (e: NumberedCancellationException) {
92-
e.testResult
93-
}
89+
fun tryReceive(): Any? =
90+
c.tryReceive()
91+
.onSuccess { it }
92+
.onFailure { if (it is NumberedCancellationException) it.testResult else throw it!! }
9493

9594
// TODO: this operation should be (and can be!) linearizable, but is not
9695
// @Operation
@@ -164,11 +163,11 @@ abstract class SequentialIntChannelBase(private val capacity: Int) : VerifierSta
164163
return false
165164
}
166165

167-
suspend fun receive(): Any = poll() ?: suspendCancellableCoroutine { cont ->
166+
suspend fun receive(): Any = tryReceive() ?: suspendCancellableCoroutine { cont ->
168167
receivers.add(cont)
169168
}
170169

171-
fun poll(): Any? {
170+
fun tryReceive(): Any? {
172171
if (buffer.isNotEmpty()) {
173172
val el = buffer.removeAt(0)
174173
resumeFirstSender().also {

0 commit comments

Comments
 (0)