Skip to content

Commit 5133b28

Browse files
qwwdfsadpablobaxter
authored andcommitted
Deprecate SendChannel.offer and ReceiveChannel.poll, replace their usages along the codebase (Kotlin#2644)
* Deprecate SendChannel.offer and replace its usages along the codebase * Deprecate ReceiveChannel.poll and replace its usages along the codebase Co-authored-by: Roman Elizarov <[email protected]> Addresses Kotlin#974
1 parent 1f2254d commit 5133b28

File tree

52 files changed

+289
-310
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+289
-310
lines changed

Diff for: kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt

+1
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ internal abstract class AbstractSendChannel<E>(
137137
}
138138

139139
override fun offer(element: E): Boolean {
140+
// Temporary migration for offer users who rely on onUndeliveredElement
140141
try {
141142
return super.offer(element)
142143
} catch (e: Throwable) {

Diff for: kotlinx-coroutines-core/common/src/channels/Channel.kt

+106-85
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import kotlin.jvm.*
2424
public interface SendChannel<in E> {
2525
/**
2626
* Returns `true` if this channel was closed by an invocation of [close]. This means that
27-
* calling [send] or [offer] will result in an exception.
27+
* calling [send] will result in an exception.
2828
*
2929
* **Note: This is an experimental api.** This property may change its semantics and/or name in the future.
3030
*/
@@ -51,7 +51,7 @@ public interface SendChannel<in E> {
5151
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
5252
*
5353
* This function can be used in [select] invocations with the [onSend] clause.
54-
* Use [offer] to try sending to this channel without waiting.
54+
* Use [trySend] to try sending to this channel without waiting.
5555
*/
5656
public suspend fun send(element: E)
5757

@@ -64,23 +64,6 @@ public interface SendChannel<in E> {
6464
*/
6565
public val onSend: SelectClause2<E, SendChannel<E>>
6666

67-
/**
68-
* Immediately adds the specified [element] to this channel, if this doesn't violate its capacity restrictions,
69-
* and returns `true`. Otherwise, just returns `false`. This is a synchronous variant of [send] which backs off
70-
* in situations when `send` suspends.
71-
*
72-
* Throws an exception if the channel [is closed for `send`][isClosedForSend] (see [close] for details).
73-
*
74-
* When `offer` call returns `false` it guarantees that the element was not delivered to the consumer and it
75-
* it does not call `onUndeliveredElement` that was installed for this channel. If the channel was closed,
76-
* then it calls `onUndeliveredElement` before throwing an exception.
77-
* See "Undelivered elements" section in [Channel] documentation for details on handling undelivered elements.
78-
*/
79-
public fun offer(element: E): Boolean {
80-
val result = trySend(element)
81-
if (result.isSuccess) return true
82-
throw recoverStackTrace(result.exceptionOrNull() ?: return false)
83-
}
8467

8568
/**
8669
* Immediately adds the specified [element] to this channel, if this doesn't violate its capacity restrictions,
@@ -103,7 +86,7 @@ public interface SendChannel<in E> {
10386
* on the side of [ReceiveChannel] starts returning `true` only after all previously sent elements
10487
* are received.
10588
*
106-
* A channel that was closed without a [cause] throws a [ClosedSendChannelException] on attempts to [send] or [offer]
89+
* A channel that was closed without a [cause] throws a [ClosedSendChannelException] on attempts to [send]
10790
* and [ClosedReceiveChannelException] on attempts to [receive][ReceiveChannel.receive].
10891
* A channel that was closed with non-null [cause] is called a _failed_ channel. Attempts to send or
10992
* receive on a failed channel throw the specified [cause] exception.
@@ -122,10 +105,11 @@ public interface SendChannel<in E> {
122105
* * the cause of `close` or `cancel` otherwise.
123106
*
124107
* Example of usage (exception handling is omitted):
108+
*
125109
* ```
126110
* val events = Channel(UNLIMITED)
127111
* callbackBasedApi.registerCallback { event ->
128-
* events.offer(event)
112+
* events.trySend(event)
129113
* }
130114
*
131115
* val uiUpdater = launch(Dispatchers.Main, parent = UILifecycle) {
@@ -134,7 +118,6 @@ public interface SendChannel<in E> {
134118
* }
135119
*
136120
* events.invokeOnClose { callbackBasedApi.stop() }
137-
*
138121
* ```
139122
*
140123
* **Note: This is an experimental api.** This function may change its semantics, parameters or return type in the future.
@@ -146,6 +129,33 @@ public interface SendChannel<in E> {
146129
*/
147130
@ExperimentalCoroutinesApi
148131
public fun invokeOnClose(handler: (cause: Throwable?) -> Unit)
132+
133+
/**
134+
* **Deprecated** offer method.
135+
*
136+
* This method was deprecated in the favour of [trySend].
137+
* It has proven itself as the most error-prone method in Channel API:
138+
*
139+
* * `Boolean` return type creates the false sense of security, implying that `false`
140+
* is returned instead of throwing an exception.
141+
* * It was used mostly from non-suspending APIs where CancellationException triggered
142+
* internal failures in the application (the most common source of bugs).
143+
* * Due to signature and explicit `if (ch.offer(...))` checks it was easy to
144+
* oversee such error during code review.
145+
* * Its name was not aligned with the rest of the API and tried to mimic Java's queue instead.
146+
*
147+
* See https://github.com/Kotlin/kotlinx.coroutines/issues/974 for more context.
148+
*/
149+
@Deprecated(
150+
level = DeprecationLevel.WARNING,
151+
message = "Deprecated in the favour of 'trySend' method",
152+
replaceWith = ReplaceWith("trySend(element).isSuccess")
153+
) // Since 1.5.0
154+
public fun offer(element: E): Boolean {
155+
val result = trySend(element)
156+
if (result.isSuccess) return true
157+
throw recoverStackTrace(result.exceptionOrNull() ?: return false)
158+
}
149159
}
150160

151161
/**
@@ -188,7 +198,7 @@ public interface ReceiveChannel<out E> {
188198
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
189199
*
190200
* This function can be used in [select] invocations with the [onReceive] clause.
191-
* Use [poll] to try receiving from this channel without waiting.
201+
* Use [tryReceive] to try receiving from this channel without waiting.
192202
*/
193203
public suspend fun receive(): E
194204

@@ -200,51 +210,6 @@ public interface ReceiveChannel<out E> {
200210
*/
201211
public val onReceive: SelectClause1<E>
202212

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

@@ -273,17 +238,6 @@ public interface ReceiveChannel<out E> {
273238
*/
274239
public val onReceiveCatching: SelectClause1<ChannelResult<E>>
275240

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

330353
/**
@@ -544,14 +567,14 @@ public interface ChannelIterator<out E> {
544567
*
545568
* * When `capacity` is [Channel.UNLIMITED] &mdash; it creates a channel with effectively unlimited buffer.
546569
* This channel has a linked-list buffer of unlimited capacity (limited only by available memory).
547-
* [Sending][send] to this channel never suspends, and [offer] always returns `true`.
570+
* [Sending][send] to this channel never suspends, and [trySend] always succeeds.
548571
*
549572
* * When `capacity` is [Channel.CONFLATED] &mdash; it creates a _conflated_ channel
550-
* This channel buffers at most one element and conflates all subsequent `send` and `offer` invocations,
573+
* This channel buffers at most one element and conflates all subsequent `send` and `trySend` invocations,
551574
* so that the receiver always gets the last element sent.
552575
* Back-to-back sent elements are conflated &mdash; only the last sent element is received,
553576
* while previously sent elements **are lost**.
554-
* [Sending][send] to this channel never suspends, and [offer] always returns `true`.
577+
* [Sending][send] to this channel never suspends, and [trySend] always succeeds.
555578
*
556579
* * When `capacity` is positive but less than [UNLIMITED] &mdash; it creates an array-based channel with the specified capacity.
557580
* This channel has an array buffer of a fixed `capacity`.
@@ -598,8 +621,6 @@ public interface ChannelIterator<out E> {
598621
*
599622
* * When [send][SendChannel.send] operation throws an exception because it was cancelled before it had a chance to actually
600623
* send the element or because the channel was [closed][SendChannel.close] or [cancelled][ReceiveChannel.cancel].
601-
* * When [offer][SendChannel.offer] operation throws an exception when
602-
* the channel was [closed][SendChannel.close] or [cancelled][ReceiveChannel.cancel].
603624
* * When [receive][ReceiveChannel.receive], [receiveOrNull][ReceiveChannel.receiveOrNull], or [hasNext][ChannelIterator.hasNext]
604625
* operation throws an exception when it had retrieved the element from the
605626
* channel but was cancelled before the code following the receive call resumed.

Diff for: kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import kotlin.jvm.*
1717
* Back-to-send sent elements are _conflated_ -- only the the most recently sent value is received,
1818
* while previously sent elements **are lost**.
1919
* Every subscriber immediately receives the most recently sent element.
20-
* Sender to this broadcast channel never suspends and [offer] always returns `true`.
20+
* Sender to this broadcast channel never suspends and [trySend] always succeeds.
2121
*
2222
* A secondary constructor can be used to create an instance of this class that already holds a value.
2323
* This channel is also created by `BroadcastChannel(Channel.CONFLATED)` factory function invocation.

Diff for: kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import kotlinx.coroutines.internal.*
99
import kotlinx.coroutines.selects.*
1010

1111
/**
12-
* Channel that buffers at most one element and conflates all subsequent `send` and `offer` invocations,
12+
* Channel that buffers at most one element and conflates all subsequent `send` and `trySend` invocations,
1313
* so that the receiver always gets the most recently sent element.
1414
* Back-to-send sent elements are _conflated_ -- only the most recently sent element is received,
1515
* while previously sent elements **are lost**.
16-
* Sender to this channel never suspends and [offer] always returns `true`.
16+
* Sender to this channel never suspends and [trySend] always succeeds.
1717
*
1818
* This channel is created by `Channel(Channel.CONFLATED)` factory function invocation.
1919
*/

Diff for: kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import kotlinx.coroutines.selects.*
99

1010
/**
1111
* Channel with linked-list buffer of a unlimited capacity (limited only by available memory).
12-
* Sender to this channel never suspends and [offer] always returns `true`.
12+
* Sender to this channel never suspends and [trySend] always succeeds.
1313
*
1414
* This channel is created by `Channel(Channel.UNLIMITED)` factory function invocation.
1515
*

Diff for: kotlinx-coroutines-core/common/src/flow/SharedFlow.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ import kotlin.native.concurrent.*
9292
*
9393
* To migrate [BroadcastChannel] usage to [SharedFlow], start by replacing usages of the `BroadcastChannel(capacity)`
9494
* constructor with `MutableSharedFlow(0, extraBufferCapacity=capacity)` (broadcast channel does not replay
95-
* values to new subscribers). Replace [send][BroadcastChannel.send] and [offer][BroadcastChannel.offer] calls
95+
* values to new subscribers). Replace [send][BroadcastChannel.send] and [trySend][BroadcastChannel.trySend] calls
9696
* with [emit][MutableStateFlow.emit] and [tryEmit][MutableStateFlow.tryEmit], and convert subscribers' code to flow operators.
9797
*
9898
* ### Concurrency

Diff for: kotlinx-coroutines-core/common/src/flow/StateFlow.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ import kotlin.native.concurrent.*
107107
*
108108
* To migrate [ConflatedBroadcastChannel] usage to [StateFlow], start by replacing usages of the `ConflatedBroadcastChannel()`
109109
* constructor with `MutableStateFlow(initialValue)`, using `null` as an initial value if you don't have one.
110-
* Replace [send][ConflatedBroadcastChannel.send] and [offer][ConflatedBroadcastChannel.offer] calls
110+
* Replace [send][ConflatedBroadcastChannel.send] and [trySend][ConflatedBroadcastChannel.trySend] calls
111111
* with updates to the state flow's [MutableStateFlow.value], and convert subscribers' code to flow operators.
112112
* You can use the [filterNotNull] operator to mimic behavior of a `ConflatedBroadcastChannel` without initial value.
113113
*

Diff for: 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

0 commit comments

Comments
 (0)