Skip to content

Commit 0a9bfac

Browse files
committed
Introduce ChannelResult and receiveCatching/onReceiveCatching
* Introduce three-state ChannelResult, a domain-specific Result class counterpart * Introduce receiveCatching/onReceiveCatching, make it public * Get rid of receiveOrClosed/onReceiveOrClosed without migrations, it was @InternalCoroutinesApi anyway Fixes #330
1 parent 187f0aa commit 0a9bfac

12 files changed

+172
-203
lines changed

kotlinx-coroutines-core/api/kotlinx-coroutines-core.api

+22-19
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,26 @@ public final class kotlinx/coroutines/channels/ChannelKt {
625625
public static synthetic fun Channel$default (ILkotlinx/coroutines/channels/BufferOverflow;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/channels/Channel;
626626
}
627627

628+
public final class kotlinx/coroutines/channels/ChannelResult {
629+
public static final field Companion Lkotlinx/coroutines/channels/ChannelResult$Companion;
630+
public static final synthetic fun box-impl (Ljava/lang/Object;)Lkotlinx/coroutines/channels/ChannelResult;
631+
public static synthetic fun constructor-impl (Ljava/lang/Object;Lkotlin/jvm/internal/DefaultConstructorMarker;)Ljava/lang/Object;
632+
public fun equals (Ljava/lang/Object;)Z
633+
public static fun equals-impl (Ljava/lang/Object;Ljava/lang/Object;)Z
634+
public static final fun equals-impl0 (Ljava/lang/Object;Ljava/lang/Object;)Z
635+
public static final fun exceptionOrNull-impl (Ljava/lang/Object;)Ljava/lang/Throwable;
636+
public static final fun getOrNull-impl (Ljava/lang/Object;)Ljava/lang/Object;
637+
public static final fun getOrThrow-impl (Ljava/lang/Object;)Ljava/lang/Object;
638+
public fun hashCode ()I
639+
public static fun hashCode-impl (Ljava/lang/Object;)I
640+
public static final fun isClosed-impl (Ljava/lang/Object;)Z
641+
public static final fun isFailure-impl (Ljava/lang/Object;)Z
642+
public static final fun isSuccess-impl (Ljava/lang/Object;)Z
643+
public fun toString ()Ljava/lang/String;
644+
public static fun toString-impl (Ljava/lang/Object;)Ljava/lang/String;
645+
public final synthetic fun unbox-impl ()Ljava/lang/Object;
646+
}
647+
628648
public final class kotlinx/coroutines/channels/ChannelsKt {
629649
public static final fun all (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
630650
public static final fun any (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -789,14 +809,14 @@ public abstract interface class kotlinx/coroutines/channels/ReceiveChannel {
789809
public abstract synthetic fun cancel (Ljava/lang/Throwable;)Z
790810
public abstract fun cancel (Ljava/util/concurrent/CancellationException;)V
791811
public abstract fun getOnReceive ()Lkotlinx/coroutines/selects/SelectClause1;
792-
public abstract fun getOnReceiveOrClosed ()Lkotlinx/coroutines/selects/SelectClause1;
812+
public abstract fun getOnReceiveCatching ()Lkotlinx/coroutines/selects/SelectClause1;
793813
public abstract fun getOnReceiveOrNull ()Lkotlinx/coroutines/selects/SelectClause1;
794814
public abstract fun isClosedForReceive ()Z
795815
public abstract fun isEmpty ()Z
796816
public abstract fun iterator ()Lkotlinx/coroutines/channels/ChannelIterator;
797817
public abstract fun poll ()Ljava/lang/Object;
798818
public abstract fun receive (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
799-
public abstract fun receiveOrClosed-WVj179g (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
819+
public abstract fun receiveCatching-JP2dKIU (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
800820
public abstract fun receiveOrNull (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
801821
}
802822

@@ -832,23 +852,6 @@ public final class kotlinx/coroutines/channels/TickerMode : java/lang/Enum {
832852
public static fun values ()[Lkotlinx/coroutines/channels/TickerMode;
833853
}
834854

835-
public final class kotlinx/coroutines/channels/ValueOrClosed {
836-
public static final field Companion Lkotlinx/coroutines/channels/ValueOrClosed$Companion;
837-
public static final synthetic fun box-impl (Ljava/lang/Object;)Lkotlinx/coroutines/channels/ValueOrClosed;
838-
public fun equals (Ljava/lang/Object;)Z
839-
public static fun equals-impl (Ljava/lang/Object;Ljava/lang/Object;)Z
840-
public static final fun equals-impl0 (Ljava/lang/Object;Ljava/lang/Object;)Z
841-
public static final fun getCloseCause-impl (Ljava/lang/Object;)Ljava/lang/Throwable;
842-
public static final fun getValue-impl (Ljava/lang/Object;)Ljava/lang/Object;
843-
public static final fun getValueOrNull-impl (Ljava/lang/Object;)Ljava/lang/Object;
844-
public fun hashCode ()I
845-
public static fun hashCode-impl (Ljava/lang/Object;)I
846-
public static final fun isClosed-impl (Ljava/lang/Object;)Z
847-
public fun toString ()Ljava/lang/String;
848-
public static fun toString-impl (Ljava/lang/Object;)Ljava/lang/String;
849-
public final synthetic fun unbox-impl ()Ljava/lang/Object;
850-
}
851-
852855
public final class kotlinx/coroutines/debug/internal/DebugCoroutineInfo {
853856
public fun <init> (Lkotlinx/coroutines/debug/internal/DebugCoroutineInfoImpl;Lkotlin/coroutines/CoroutineContext;)V
854857
public final fun getContext ()Lkotlin/coroutines/CoroutineContext;

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

+13-13
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ internal abstract class AbstractChannel<E>(
623623
}
624624

625625
@Suppress("UNCHECKED_CAST")
626-
public final override suspend fun receiveOrClosed(): ValueOrClosed<E> {
626+
public final override suspend fun receiveCatching(): ChannelResult<E> {
627627
// fast path -- try poll non-blocking
628628
val result = pollInternal()
629629
if (result !== POLL_FAILED) return result.toResult()
@@ -742,10 +742,10 @@ internal abstract class AbstractChannel<E>(
742742
}
743743
}
744744

745-
final override val onReceiveOrClosed: SelectClause1<ValueOrClosed<E>>
746-
get() = object : SelectClause1<ValueOrClosed<E>> {
745+
final override val onReceiveCatching: SelectClause1<ChannelResult<E>>
746+
get() = object : SelectClause1<ChannelResult<E>> {
747747
@Suppress("UNCHECKED_CAST")
748-
override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (ValueOrClosed<E>) -> R) {
748+
override fun <R> registerSelectClause1(select: SelectInstance<R>, block: suspend (ChannelResult<E>) -> R) {
749749
registerSelectReceiveMode(select, RECEIVE_RESULT, block as suspend (Any?) -> R)
750750
}
751751
}
@@ -776,7 +776,7 @@ internal abstract class AbstractChannel<E>(
776776
}
777777
RECEIVE_RESULT -> {
778778
if (!select.trySelect()) return
779-
startCoroutineUnintercepted(ValueOrClosed.closed<Any>(value.closeCause), select.completion)
779+
startCoroutineUnintercepted(ChannelResult.closed<Any>(value.closeCause), select.completion)
780780
}
781781
RECEIVE_NULL_ON_CLOSE -> {
782782
if (value.closeCause == null) {
@@ -905,7 +905,7 @@ internal abstract class AbstractChannel<E>(
905905
@JvmField val receiveMode: Int
906906
) : Receive<E>() {
907907
fun resumeValue(value: E): Any? = when (receiveMode) {
908-
RECEIVE_RESULT -> ValueOrClosed.value(value)
908+
RECEIVE_RESULT -> ChannelResult.value(value)
909909
else -> value
910910
}
911911

@@ -990,7 +990,7 @@ internal abstract class AbstractChannel<E>(
990990
@Suppress("UNCHECKED_CAST")
991991
override fun completeResumeReceive(value: E) {
992992
block.startCoroutineCancellable(
993-
if (receiveMode == RECEIVE_RESULT) ValueOrClosed.value(value) else value,
993+
if (receiveMode == RECEIVE_RESULT) ChannelResult.value(value) else value,
994994
select.completion,
995995
resumeOnCancellationFun(value)
996996
)
@@ -1000,7 +1000,7 @@ internal abstract class AbstractChannel<E>(
10001000
if (!select.trySelect()) return
10011001
when (receiveMode) {
10021002
RECEIVE_THROWS_ON_CLOSE -> select.resumeSelectWithException(closed.receiveException)
1003-
RECEIVE_RESULT -> block.startCoroutineCancellable(ValueOrClosed.closed<R>(closed.closeCause), select.completion)
1003+
RECEIVE_RESULT -> block.startCoroutineCancellable(ChannelResult.closed<R>(closed.closeCause), select.completion)
10041004
RECEIVE_NULL_ON_CLOSE -> if (closed.closeCause == null) {
10051005
block.startCoroutineCancellable(null, select.completion)
10061006
} else {
@@ -1128,9 +1128,9 @@ internal class Closed<in E>(
11281128

11291129
override val offerResult get() = this
11301130
override val pollResult get() = this
1131-
override fun tryResumeSend(otherOp: PrepareOp?): Symbol? = RESUME_TOKEN.also { otherOp?.finishPrepare() }
1131+
override fun tryResumeSend(otherOp: PrepareOp?): Symbol = RESUME_TOKEN.also { otherOp?.finishPrepare() }
11321132
override fun completeResumeSend() {}
1133-
override fun tryResumeReceive(value: E, otherOp: PrepareOp?): Symbol? = RESUME_TOKEN.also { otherOp?.finishPrepare() }
1133+
override fun tryResumeReceive(value: E, otherOp: PrepareOp?): Symbol = RESUME_TOKEN.also { otherOp?.finishPrepare() }
11341134
override fun completeResumeReceive(value: E) {}
11351135
override fun resumeSendClosed(closed: Closed<*>) = assert { false } // "Should be never invoked"
11361136
override fun toString(): String = "Closed@$hexAddress[$closeCause]"
@@ -1143,8 +1143,8 @@ internal abstract class Receive<in E> : LockFreeLinkedListNode(), ReceiveOrClose
11431143
}
11441144

11451145
@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST")
1146-
private inline fun <E> Any?.toResult(): ValueOrClosed<E> =
1147-
if (this is Closed<*>) ValueOrClosed.closed(closeCause) else ValueOrClosed.value(this as E)
1146+
private inline fun <E> Any?.toResult(): ChannelResult<E> =
1147+
if (this is Closed<*>) ChannelResult.closed(closeCause) else ChannelResult.value(this as E)
11481148

11491149
@Suppress("NOTHING_TO_INLINE")
1150-
private inline fun <E> Closed<*>.toResult(): ValueOrClosed<E> = ValueOrClosed.closed(closeCause)
1150+
private inline fun <E> Closed<*>.toResult(): ChannelResult<E> = ChannelResult.closed(closeCause)

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

+46-80
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ public interface ReceiveChannel<out E> {
244244

245245
/**
246246
* Retrieves and removes an element from this channel if it's not empty, or suspends the caller while this channel is empty.
247-
* This method returns [ValueOrClosed] with the value of an element successfully retrieved from the channel
248-
* or the close cause if the channel was closed.
247+
* This method returns [ChannelResult] with the value of an element successfully retrieved from the channel
248+
* or the close cause if the channel was closed. Closed cause may be `null` if the channel was closed normally.
249249
*
250250
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
251251
* function is suspended, this function immediately resumes with a [CancellationException].
@@ -257,25 +257,17 @@ public interface ReceiveChannel<out E> {
257257
* Note that this function does not check for cancellation when it is not suspended.
258258
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
259259
*
260-
* This function can be used in [select] invocations with the [onReceiveOrClosed] clause.
260+
* This function can be used in [select] invocations with the [onReceiveCatching] clause.
261261
* Use [poll] to try receiving from this channel without waiting.
262-
*
263-
* @suppress *This is an internal API, do not use*: Inline classes ABI is not stable yet and
264-
* [KT-27524](https://youtrack.jetbrains.com/issue/KT-27524) needs to be fixed.
265262
*/
266-
@InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
267-
public suspend fun receiveOrClosed(): ValueOrClosed<E>
263+
public suspend fun receiveCatching(): ChannelResult<E>
268264

269265
/**
270-
* Clause for the [select] expression of the [receiveOrClosed] suspending function that selects with the [ValueOrClosed] with a value
266+
* Clause for the [select] expression of the [onReceiveCatching] suspending function that selects with the [ChannelResult] with a value
271267
* that is received from the channel or with a close cause if the channel
272268
* [is closed for `receive`][isClosedForReceive].
273-
*
274-
* @suppress *This is an internal API, do not use*: Inline classes ABI is not stable yet and
275-
* [KT-27524](https://youtrack.jetbrains.com/issue/KT-27524) needs to be fixed.
276269
*/
277-
@InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
278-
public val onReceiveOrClosed: SelectClause1<ValueOrClosed<E>>
270+
public val onReceiveCatching: SelectClause1<ChannelResult<E>>
279271

280272
/**
281273
* Retrieves and removes an element from this channel if its not empty, or returns `null` if the channel is empty
@@ -321,104 +313,78 @@ public interface ReceiveChannel<out E> {
321313
}
322314

323315
/**
324-
* A discriminated union of [ReceiveChannel.receiveOrClosed] result
325-
* that encapsulates either an element of type [T] successfully received from the channel or a close cause.
326-
*
327-
* :todo: Do not make it public before resolving todos in the code of this class.
316+
* A discriminated union of channel operation result.
317+
* It encapsulates either a successful result of the channel operation or a closed cause of a channel.
328318
*
329-
* @suppress *This is an internal API, do not use*: Inline classes ABI is not stable yet and
330-
* [KT-27524](https://youtrack.jetbrains.com/issue/KT-27524) needs to be fixed.
319+
* Successful result may represent a successfully received value of type [T], for example as a result of [Channel.receiveCatching]
320+
* operation or a successfully sent element as a result of [Channel.trySend].
331321
*/
332-
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS", "EXPERIMENTAL_FEATURE_WARNING")
333-
@InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
334-
public inline class ValueOrClosed<out T>
335-
internal constructor(private val holder: Any?) {
336-
/**
337-
* Returns `true` if this instance represents a received element.
338-
* In this case [isClosed] returns `false`.
339-
* todo: it is commented for now, because it is not used
340-
*/
341-
//public val isValue: Boolean get() = holder !is Closed
342-
322+
@Suppress("UNCHECKED_CAST")
323+
public inline class ChannelResult<out T>
324+
private constructor(private val holder: Any?) {
343325
/**
344-
* Returns `true` if this instance represents a close cause.
345-
* In this case [isValue] returns `false`.
326+
* Returns `true` if this instance represents a successful
327+
* operation outcome.
328+
*
329+
* In this case [isFailure] and [isClosed] return false.
346330
*/
347-
public val isClosed: Boolean get() = holder is Closed
331+
public val isSuccess: Boolean get() = holder !is Closed
348332

349333
/**
350-
* Returns the received value if this instance represents a received value, or throws an [IllegalStateException] otherwise.
351-
*
352-
* :todo: Decide, if it is needed, how it shall be named with relation to [valueOrThrow]:
334+
* Returns true if this instance represents a failed outcome.
353335
*
354-
* So we have the following methods on `ValueOrClosed`: `value`, `valueOrNull`, `valueOrThrow`.
355-
* On the other hand, the channel has the following `receive` variants:
356-
* * `receive` which corresponds to `receiveOrClosed().valueOrThrow`... huh?
357-
* * `receiveOrNull` which corresponds to `receiveOrClosed().valueOrNull`
358-
* * `receiveOrClosed`
359-
* For the sake of simplicity consider dropping this version of `value` and rename [valueOrThrow] to simply `value`.
336+
* In this case [isSuccess] returns false and [isClosed] returns true,
337+
* but it does not imply that [exceptionOrNull] will return non-null value.
360338
*/
361-
@Suppress("UNCHECKED_CAST")
362-
public val value: T
363-
get() = if (holder is Closed) error(DEFAULT_CLOSE_MESSAGE) else holder as T
339+
public val isFailure: Boolean get() = holder is Closed && holder.cause != null
364340

365341
/**
366-
* Returns the received value if this element represents a received value, or `null` otherwise.
367-
* :todo: Decide if it shall be made into extension that is available only for non-null T.
368-
* Note: it might become inconsistent with kotlin.Result
342+
* Returns `true` if this instance represents unsuccessful operation
343+
* to closed channel.
344+
*
345+
* In this case [isSuccess] returns false, but it doesn't imply
346+
* that [isFailure] returns true or that [exceptionOrNull] returns non-null value.
347+
* It can happen if the channel was [closed][Channel.close] normally without an exception.
369348
*/
370-
@Suppress("UNCHECKED_CAST")
371-
public val valueOrNull: T?
372-
get() = if (holder is Closed) null else holder as T
349+
public val isClosed: Boolean get() = holder is Closed
373350

374351
/**
375-
* :todo: Decide, if it is needed, how it shall be named with relation to [value].
376-
* Note that `valueOrThrow` rethrows the cause adding no meaningful information about the call site,
377-
* so if one is sure that `ValueOrClosed` always holds a value, this very property should be used.
378-
* Otherwise, it could be very hard to locate the source of the exception.
379-
* todo: it is commented for now, because it is not used
352+
* Returns the encapsulated value if this instance represents success or `null` if it is closed or failed.
380353
*/
381-
//@Suppress("UNCHECKED_CAST")
382-
//public val valueOrThrow: T
383-
// get() = if (holder is Closed) throw holder.exception else holder as T
354+
public fun getOrNull(): T? = if (holder !is Closed) holder as T else null
384355

385356
/**
386-
* Returns the close cause of the channel if this instance represents a close cause, or throws
387-
* an [IllegalStateException] otherwise.
357+
* Returns the encapsulated value if this instance represents success or throws an exception if it is closed or failed.
388358
*/
389-
@Suppress("UNCHECKED_CAST")
390-
public val closeCause: Throwable? get() =
391-
if (holder is Closed) holder.cause else error("Channel was not closed")
359+
public fun getOrThrow(): T = if (holder !is Closed) holder as T else error("Trying to call 'getOrThrow' on a closed channel result")
392360

393361
/**
394-
* @suppress
362+
* Returns the encapsulated exception if this instance represents failure or null if it is success
363+
* or unsuccessful operation to closed channel.
395364
*/
396-
public override fun toString(): String =
397-
when (holder) {
398-
is Closed -> holder.toString()
399-
else -> "Value($holder)"
400-
}
365+
public fun exceptionOrNull(): Throwable? = (holder as? Closed)?.cause
401366

402367
internal class Closed(@JvmField val cause: Throwable?) {
403-
// todo: it is commented for now, because it is not used
404-
//val exception: Throwable get() = cause ?: ClosedReceiveChannelException(DEFAULT_CLOSE_MESSAGE)
405368
override fun equals(other: Any?): Boolean = other is Closed && cause == other.cause
406369
override fun hashCode(): Int = cause.hashCode()
407370
override fun toString(): String = "Closed($cause)"
408371
}
409372

410-
/**
411-
* todo: consider making value/closed constructors public in the future.
412-
*/
413373
internal companion object {
414374
@Suppress("NOTHING_TO_INLINE")
415-
internal inline fun <E> value(value: E): ValueOrClosed<E> =
416-
ValueOrClosed(value)
375+
internal inline fun <E> value(value: E): ChannelResult<E> =
376+
ChannelResult(value)
417377

418378
@Suppress("NOTHING_TO_INLINE")
419-
internal inline fun <E> closed(cause: Throwable?): ValueOrClosed<E> =
420-
ValueOrClosed(Closed(cause))
379+
internal inline fun <E> closed(cause: Throwable?): ChannelResult<E> =
380+
ChannelResult(Closed(cause))
421381
}
382+
383+
public override fun toString(): String =
384+
when (holder) {
385+
is Closed -> holder.toString()
386+
else -> "Value($holder)"
387+
}
422388
}
423389

424390
/**

0 commit comments

Comments
 (0)