Skip to content

Commit ed8e8d1

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 e608dfb commit ed8e8d1

13 files changed

+299
-300
lines changed

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

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

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

+61-76
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,9 @@ 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.
249+
* The result cannot be [failed][ChannelResult.isFailure] without being [closed][ChannelResult.isClosed].
249250
*
250251
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
251252
* function is suspended, this function immediately resumes with a [CancellationException].
@@ -257,25 +258,17 @@ public interface ReceiveChannel<out E> {
257258
* Note that this function does not check for cancellation when it is not suspended.
258259
* Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
259260
*
260-
* This function can be used in [select] invocations with the [onReceiveOrClosed] clause.
261+
* This function can be used in [select] invocations with the [onReceiveCatching] clause.
261262
* 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.
265263
*/
266-
@InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
267-
public suspend fun receiveOrClosed(): ValueOrClosed<E>
264+
public suspend fun receiveCatching(): ChannelResult<E>
268265

269266
/**
270-
* Clause for the [select] expression of the [receiveOrClosed] suspending function that selects with the [ValueOrClosed] with a value
267+
* Clause for the [select] expression of the [onReceiveCatching] suspending function that selects with the [ChannelResult] with a value
271268
* that is received from the channel or with a close cause if the channel
272269
* [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.
276270
*/
277-
@InternalCoroutinesApi // until https://youtrack.jetbrains.com/issue/KT-27524 is fixed
278-
public val onReceiveOrClosed: SelectClause1<ValueOrClosed<E>>
271+
public val onReceiveCatching: SelectClause1<ChannelResult<E>>
279272

280273
/**
281274
* Retrieves and removes an element from this channel if its not empty, or returns `null` if the channel is empty
@@ -321,104 +314,96 @@ public interface ReceiveChannel<out E> {
321314
}
322315

323316
/**
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.
317+
* A discriminated union of channel operation result.
318+
* It encapsulates successful or failed result of a channel operation, or a failed operation to a closed channel with
319+
* an optional cause.
320+
*
321+
* Successful result represents a successful operation with value of type [T], for example, result of [Channel.receiveCatching]
322+
* operation or a successfully sent element as a result of [Channel.trySend].
326323
*
327-
* :todo: Do not make it public before resolving todos in the code of this class.
324+
* Failed result represents a failed operation attempt to a channel, but it doesn't necessary indicate that the channel is failed.
325+
* E.g. when the channel is full, [Channel.trySend] returns failed result, but the channel itself is not in the failed state.
328326
*
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.
327+
* Closed result represents an operation attempt to a closed channel and also implies that the operation was failed.
331328
*/
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>
329+
@Suppress("UNCHECKED_CAST")
330+
public inline class ChannelResult<out T>
335331
internal constructor(private val holder: Any?) {
336332
/**
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
333+
* Returns `true` if this instance represents a successful
334+
* operation outcome.
335+
*
336+
* In this case [isFailure] and [isClosed] return false.
340337
*/
341-
//public val isValue: Boolean get() = holder !is Closed
338+
public val isSuccess: Boolean get() = holder !is Closed
342339

343340
/**
344-
* Returns `true` if this instance represents a close cause.
345-
* In this case [isValue] returns `false`.
341+
* Returns true if this instance represents unsuccessful operation.
342+
*
343+
* In this case [isSuccess] returns false, but it does not imply
344+
* that the channel is failed or closed.
345+
*
346+
* Example of failed operation without an exception and channel being closed
347+
* is [Channel.trySend] attempt to a channel that is full.
346348
*/
347-
public val isClosed: Boolean get() = holder is Closed
349+
public val isFailure: Boolean get() = holder is Failed
348350

349351
/**
350-
* Returns the received value if this instance represents a received value, or throws an [IllegalStateException] otherwise.
352+
* Returns `true` if this instance represents unsuccessful operation
353+
* to a closed or cancelled channel.
351354
*
352-
* :todo: Decide, if it is needed, how it shall be named with relation to [valueOrThrow]:
355+
* In this case [isSuccess] returns false, [isFailure] returns `true`, but it does not imply
356+
* that [exceptionOrNull] returns non-null value.
353357
*
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`.
358+
* It can happen if the channel was [closed][Channel.close] normally without an exception.
360359
*/
361-
@Suppress("UNCHECKED_CAST")
362-
public val value: T
363-
get() = if (holder is Closed) error(DEFAULT_CLOSE_MESSAGE) else holder as T
360+
public val isClosed: Boolean get() = holder is Closed
364361

365362
/**
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
363+
* Returns the encapsulated value if this instance represents success or `null` if it represents failed result.
369364
*/
370-
@Suppress("UNCHECKED_CAST")
371-
public val valueOrNull: T?
372-
get() = if (holder is Closed) null else holder as T
365+
public fun getOrNull(): T? = if (holder !is Failed) holder as T else null
373366

374367
/**
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
368+
* Returns the encapsulated value if this instance represents success or throws an exception if it is closed or failed.
380369
*/
381-
//@Suppress("UNCHECKED_CAST")
382-
//public val valueOrThrow: T
383-
// get() = if (holder is Closed) throw holder.exception else holder as T
370+
public fun getOrThrow(): T {
371+
if (holder !is Failed) return holder as T
372+
if (holder is Closed && holder.cause != null) throw holder.cause
373+
error("Trying to call 'getOrThrow' on a failed channel result: $holder")
374+
}
384375

385376
/**
386-
* Returns the close cause of the channel if this instance represents a close cause, or throws
387-
* an [IllegalStateException] otherwise.
377+
* Returns the encapsulated exception if this instance represents failure or null if it is success
378+
* or unsuccessful operation to closed channel.
388379
*/
389-
@Suppress("UNCHECKED_CAST")
390-
public val closeCause: Throwable? get() =
391-
if (holder is Closed) holder.cause else error("Channel was not closed")
380+
public fun exceptionOrNull(): Throwable? = (holder as? Closed)?.cause
392381

393-
/**
394-
* @suppress
395-
*/
396-
public override fun toString(): String =
397-
when (holder) {
398-
is Closed -> holder.toString()
399-
else -> "Value($holder)"
382+
internal open class Failed {
383+
override fun toString(): String = "Failed"
400384
}
401385

402-
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)
386+
internal class Closed(@JvmField val cause: Throwable?): Failed() {
405387
override fun equals(other: Any?): Boolean = other is Closed && cause == other.cause
406388
override fun hashCode(): Int = cause.hashCode()
407389
override fun toString(): String = "Closed($cause)"
408390
}
409391

410-
/**
411-
* todo: consider making value/closed constructors public in the future.
412-
*/
413392
internal companion object {
414393
@Suppress("NOTHING_TO_INLINE")
415-
internal inline fun <E> value(value: E): ValueOrClosed<E> =
416-
ValueOrClosed(value)
394+
internal inline fun <E> value(value: E): ChannelResult<E> =
395+
ChannelResult(value)
417396

418397
@Suppress("NOTHING_TO_INLINE")
419-
internal inline fun <E> closed(cause: Throwable?): ValueOrClosed<E> =
420-
ValueOrClosed(Closed(cause))
398+
internal inline fun <E> closed(cause: Throwable?): ChannelResult<E> =
399+
ChannelResult(Closed(cause))
421400
}
401+
402+
public override fun toString(): String =
403+
when (holder) {
404+
is Closed -> holder.toString()
405+
else -> "Value($holder)"
406+
}
422407
}
423408

424409
/**

0 commit comments

Comments
 (0)