Skip to content

Commit dd8ce2e

Browse files
qwwdfsadelizarov
authored andcommitted
Channel.receiveOrNull becomes extension, internal receiveOrClosed added
* The corresponding ReceiveChannel methods are deprecated. * Introduced corresponding extensions with the same semantic and generic Any bound. * Introduce internal ReceiveChannel.[on]receiveOrClosed * Using internal inline class ValueOrClosed. * To be stabilized and made public in the future when inline classes ABI stabilizes. * It is related to #330 but does not resolve it yet. * Includes todos for future public ValueOrClose design.
1 parent bb4a835 commit dd8ce2e

File tree

14 files changed

+709
-68
lines changed

14 files changed

+709
-68
lines changed

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

+23
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,9 @@ public final class kotlinx/coroutines/channels/ChannelsKt {
670670
public static final fun minWith (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Comparator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
671671
public static final fun none (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
672672
public static final fun none (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
673+
public static final fun onReceiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/selects/SelectClause1;
673674
public static final fun partition (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
675+
public static final fun receiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
674676
public static final fun reduce (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
675677
public static final fun reduceIndexed (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
676678
public static final fun requireNoNulls (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/channels/ReceiveChannel;
@@ -744,12 +746,14 @@ public abstract interface class kotlinx/coroutines/channels/ReceiveChannel {
744746
public abstract fun cancel (Ljava/util/concurrent/CancellationException;)V
745747
public abstract fun consumeEachTo (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
746748
public abstract fun getOnReceive ()Lkotlinx/coroutines/selects/SelectClause1;
749+
public abstract fun getOnReceiveOrClosed ()Lkotlinx/coroutines/selects/SelectClause1;
747750
public abstract fun getOnReceiveOrNull ()Lkotlinx/coroutines/selects/SelectClause1;
748751
public abstract fun isClosedForReceive ()Z
749752
public abstract fun isEmpty ()Z
750753
public abstract fun iterator ()Lkotlinx/coroutines/channels/ChannelIterator;
751754
public abstract fun poll ()Ljava/lang/Object;
752755
public abstract fun receive (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
756+
public abstract fun receiveOrClosed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
753757
public abstract fun receiveOrNull (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
754758
}
755759

@@ -785,6 +789,25 @@ public final class kotlinx/coroutines/channels/TickerMode : java/lang/Enum {
785789
public static fun values ()[Lkotlinx/coroutines/channels/TickerMode;
786790
}
787791

792+
public final class kotlinx/coroutines/channels/ValueOrClosed {
793+
public static final field Companion Lkotlinx/coroutines/channels/ValueOrClosed$Companion;
794+
public static final synthetic fun box-impl (Ljava/lang/Object;)Lkotlinx/coroutines/channels/ValueOrClosed;
795+
public fun equals (Ljava/lang/Object;)Z
796+
public static fun equals-impl (Ljava/lang/Object;Ljava/lang/Object;)Z
797+
public static final fun equals-impl0 (Ljava/lang/Object;Ljava/lang/Object;)Z
798+
public static final fun getCloseCause-impl (Ljava/lang/Object;)Ljava/lang/Throwable;
799+
public static final fun getValue-impl (Ljava/lang/Object;)Ljava/lang/Object;
800+
public static final fun getValueOrNull-impl (Ljava/lang/Object;)Ljava/lang/Object;
801+
public static final fun getValueOrThrow-impl (Ljava/lang/Object;)Ljava/lang/Object;
802+
public fun hashCode ()I
803+
public static fun hashCode-impl (Ljava/lang/Object;)I
804+
public static final fun isClosed-impl (Ljava/lang/Object;)Z
805+
public static final fun isValue-impl (Ljava/lang/Object;)Z
806+
public fun toString ()Ljava/lang/String;
807+
public static fun toString-impl (Ljava/lang/Object;)Ljava/lang/String;
808+
public final synthetic fun unbox-impl ()Ljava/lang/Object;
809+
}
810+
788811
public abstract class kotlinx/coroutines/flow/AbstractFlow : kotlinx/coroutines/flow/Flow {
789812
public fun <init> ()V
790813
public final fun collect (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;

build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ allprojects {
123123
def platform = platformOf(it)
124124
apply from: rootProject.file("gradle/compile-${platform}.gradle")
125125

126-
127126
dependencies {
128127
// See comment below for rationale, it will be replaced with "project" dependency
129128
compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version"
@@ -135,6 +134,7 @@ allprojects {
135134
tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
136135
kotlinOptions.freeCompilerArgs += experimentalAnnotations.collect { "-Xuse-experimental=" + it }
137136
kotlinOptions.freeCompilerArgs += "-progressive"
137+
kotlinOptions.freeCompilerArgs += "-XXLanguage:+InlineClasses"
138138
// Binary compatibility support
139139
kotlinOptions.freeCompilerArgs += ["-Xdump-declarations-to=${buildDir}/visibilities.json"]
140140
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.channels
6+
7+
import kotlinx.coroutines.*
8+
import kotlin.test.*
9+
10+
class ChannelReceiveOrClosedTest : TestBase() {
11+
@Test
12+
fun testChannelOfThrowables() = runTest {
13+
val channel = Channel<Throwable>()
14+
launch {
15+
channel.send(TestException1())
16+
channel.close(TestException2())
17+
}
18+
19+
val element = channel.receiveOrClosed()
20+
assertTrue(element.isValue)
21+
assertTrue(element.value is TestException1)
22+
assertTrue(element.valueOrNull is TestException1)
23+
24+
val closed = channel.receiveOrClosed()
25+
assertTrue(closed.isClosed)
26+
assertTrue(closed.closeCause is TestException2)
27+
}
28+
29+
@Test
30+
@Suppress("ReplaceAssertBooleanWithAssertEquality") // inline classes test
31+
fun testNullableIntChanel() = runTest {
32+
val channel = Channel<Int?>()
33+
launch {
34+
expect(2)
35+
channel.send(1)
36+
expect(3)
37+
channel.send(null)
38+
39+
expect(6)
40+
channel.close()
41+
}
42+
43+
expect(1)
44+
val element = channel.receiveOrClosed()
45+
assertTrue(element.isValue)
46+
assertEquals(1, element.value)
47+
assertEquals(1, element.valueOrNull)
48+
assertEquals("Value(1)", element.toString())
49+
assertTrue(ValueOrClosed.value(1) == element) // Don't box
50+
51+
expect(4)
52+
val nullElement = channel.receiveOrClosed()
53+
assertTrue(nullElement.isValue)
54+
assertNull(nullElement.value)
55+
assertNull(nullElement.valueOrNull)
56+
assertEquals("Value(null)", nullElement.toString())
57+
assertTrue(ValueOrClosed.value(null) == nullElement) // Don't box
58+
59+
expect(5)
60+
val closed = channel.receiveOrClosed()
61+
assertTrue(closed.isClosed)
62+
63+
val closed2 = channel.receiveOrClosed()
64+
assertTrue(closed2.isClosed)
65+
assertTrue(closed2.closeCause is ClosedReceiveChannelException)
66+
finish(7)
67+
}
68+
69+
@Test
70+
@ExperimentalUnsignedTypes
71+
fun testUIntChannel() = runTest {
72+
val channel = Channel<UInt>()
73+
launch {
74+
expect(2)
75+
channel.send(1u)
76+
yield()
77+
expect(4)
78+
channel.send((Long.MAX_VALUE - 1).toUInt())
79+
expect(5)
80+
}
81+
82+
expect(1)
83+
val element = channel.receiveOrClosed()
84+
assertEquals(1u, element.value)
85+
86+
expect(3)
87+
val element2 = channel.receiveOrClosed()
88+
assertEquals((Long.MAX_VALUE - 1).toUInt(), element2.value)
89+
finish(6)
90+
}
91+
92+
@Test
93+
fun testCancelChannel() = runTest {
94+
val channel = Channel<Boolean>()
95+
launch {
96+
expect(2)
97+
channel.cancel()
98+
}
99+
100+
expect(1)
101+
val closed = channel.receiveOrClosed()
102+
assertTrue(closed.isClosed)
103+
assertTrue(closed.closeCause is ClosedReceiveChannelException)
104+
finish(3)
105+
}
106+
107+
@Test
108+
@ExperimentalUnsignedTypes
109+
fun testReceiveResultChannel() = runTest {
110+
val channel = Channel<ValueOrClosed<UInt>>()
111+
launch {
112+
channel.send(ValueOrClosed.value(1u))
113+
channel.send(ValueOrClosed.closed(TestException1()))
114+
channel.close(TestException2())
115+
}
116+
117+
val intResult = channel.receiveOrClosed()
118+
assertTrue(intResult.isValue)
119+
assertEquals(1u, intResult.value.value)
120+
121+
val closeCauseResult = channel.receiveOrClosed()
122+
assertTrue(closeCauseResult.isValue)
123+
assertTrue(closeCauseResult.value.closeCause is TestException1)
124+
125+
val closeCause = channel.receiveOrClosed()
126+
assertTrue(closeCause.isClosed)
127+
assertTrue(closeCause.closeCause is TestException2)
128+
assertFailsWith<TestException2> { closeCause.valueOrThrow }
129+
}
130+
131+
@Test
132+
fun testToString() = runTest {
133+
val channel = Channel<String>(1)
134+
channel.send("message")
135+
channel.close(TestException1())
136+
assertEquals("Value(message)", channel.receiveOrClosed().toString())
137+
// toString implementation for exception differs on every platform
138+
val str = channel.receiveOrClosed().toString()
139+
assertTrue(str.matches("Closed\\(.*TestException1\\)".toRegex()))
140+
}
141+
}

docs/select-expression.md

+8-4
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ buzz -> 'Buzz!'
163163
### Selecting on close
164164

165165
The [onReceive][ReceiveChannel.onReceive] clause in `select` fails when the channel is closed causing the corresponding
166-
`select` to throw an exception. We can use [onReceiveOrNull][ReceiveChannel.onReceiveOrNull] clause to perform a
166+
`select` to throw an exception. We can use [onReceiveOrNull][onReceiveOrNull] clause to perform a
167167
specific action when the channel is closed. The following example also shows that `select` is an expression that returns
168168
the result of its selected clause:
169169

@@ -189,6 +189,10 @@ suspend fun selectAorB(a: ReceiveChannel<String>, b: ReceiveChannel<String>): St
189189

190190
</div>
191191

192+
Note that [onReceiveOrNull][onReceiveOrNull] is an extension function defined only
193+
for channels with non-nullable elements so that there is no accidental confusion between a closed channel
194+
and a null value.
195+
192196
Let's use it with channel `a` that produces "Hello" string four times and
193197
channel `b` that produces "World" four times:
194198

@@ -259,7 +263,7 @@ the first one among them gets selected. Here, both channels are constantly produ
259263
being the first clause in select, wins. However, because we are using unbuffered channel, the `a` gets suspended from
260264
time to time on its [send][SendChannel.send] invocation and gives a chance for `b` to send, too.
261265

262-
The second observation, is that [onReceiveOrNull][ReceiveChannel.onReceiveOrNull] gets immediately selected when the
266+
The second observation, is that [onReceiveOrNull][onReceiveOrNull] gets immediately selected when the
263267
channel is already closed.
264268

265269
### Selecting to send
@@ -433,7 +437,7 @@ Deferred 4 produced answer 'Waited for 128 ms'
433437

434438
Let us write a channel producer function that consumes a channel of deferred string values, waits for each received
435439
deferred value, but only until the next deferred value comes over or the channel is closed. This example puts together
436-
[onReceiveOrNull][ReceiveChannel.onReceiveOrNull] and [onAwait][Deferred.onAwait] clauses in the same `select`:
440+
[onReceiveOrNull][onReceiveOrNull] and [onAwait][Deferred.onAwait] clauses in the same `select`:
437441

438442
<div class="sample" markdown="1" theme="idea" data-highlight-only>
439443

@@ -556,7 +560,7 @@ Channel was closed
556560
<!--- INDEX kotlinx.coroutines.channels -->
557561
[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
558562
[ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
559-
[ReceiveChannel.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-or-null.html
563+
[onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/on-receive-or-null.html
560564
[SendChannel.send]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/send.html
561565
[SendChannel.onSend]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/on-send.html
562566
<!--- INDEX kotlinx.coroutines.selects -->

kotlinx-coroutines-core/README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ helper function. [NonCancellable] job object is provided to suppress cancellatio
5656
| [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait] | [isCompleted][Job.isCompleted]
5757
| [SendChannel][kotlinx.coroutines.channels.SendChannel] | [send][kotlinx.coroutines.channels.SendChannel.send] | [onSend][kotlinx.coroutines.channels.SendChannel.onSend] | [offer][kotlinx.coroutines.channels.SendChannel.offer]
5858
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
59-
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.channels.ReceiveChannel.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.channels.ReceiveChannel.onReceiveOrNull] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
59+
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.channels.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.channels.onReceiveOrNull] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
6060
| [Mutex][kotlinx.coroutines.sync.Mutex] | [lock][kotlinx.coroutines.sync.Mutex.lock] | [onLock][kotlinx.coroutines.sync.Mutex.onLock] | [tryLock][kotlinx.coroutines.sync.Mutex.tryLock]
6161
| none | [delay] | [onTimeout][kotlinx.coroutines.selects.SelectBuilder.onTimeout] | none
6262

@@ -131,8 +131,8 @@ Obsolete and deprecated module to test coroutines. Replaced with `kotlinx-corout
131131
[kotlinx.coroutines.channels.SendChannel.offer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/offer.html
132132
[kotlinx.coroutines.channels.ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
133133
[kotlinx.coroutines.channels.ReceiveChannel.poll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/poll.html
134-
[kotlinx.coroutines.channels.ReceiveChannel.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive-or-null.html
135-
[kotlinx.coroutines.channels.ReceiveChannel.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-or-null.html
134+
[kotlinx.coroutines.channels.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/receive-or-null.html
135+
[kotlinx.coroutines.channels.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/on-receive-or-null.html
136136
<!--- INDEX kotlinx.coroutines.selects -->
137137
[kotlinx.coroutines.selects.select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
138138
[kotlinx.coroutines.selects.SelectBuilder.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/-select-builder/on-timeout.html

kotlinx-coroutines-core/common/README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ helper function. [NonCancellable] job object is provided to suppress cancellatio
5959
| [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait] | [isCompleted][Job.isCompleted]
6060
| [SendChannel][kotlinx.coroutines.channels.SendChannel] | [send][kotlinx.coroutines.channels.SendChannel.send] | [onSend][kotlinx.coroutines.channels.SendChannel.onSend] | [offer][kotlinx.coroutines.channels.SendChannel.offer]
6161
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
62-
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.channels.ReceiveChannel.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.channels.ReceiveChannel.onReceiveOrNull] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
62+
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveOrNull][kotlinx.coroutines.channels.receiveOrNull] | [onReceiveOrNull][kotlinx.coroutines.channels.onReceiveOrNull] | [poll][kotlinx.coroutines.channels.ReceiveChannel.poll]
6363
| [Mutex][kotlinx.coroutines.sync.Mutex] | [lock][kotlinx.coroutines.sync.Mutex.lock] | [onLock][kotlinx.coroutines.sync.Mutex.onLock] | [tryLock][kotlinx.coroutines.sync.Mutex.tryLock]
6464
| none | [delay] | [onTimeout][kotlinx.coroutines.selects.SelectBuilder.onTimeout] | none
6565

@@ -143,8 +143,8 @@ Low-level primitives for finer-grained control of coroutines.
143143
[kotlinx.coroutines.channels.SendChannel.offer]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-send-channel/offer.html
144144
[kotlinx.coroutines.channels.ReceiveChannel.onReceive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive.html
145145
[kotlinx.coroutines.channels.ReceiveChannel.poll]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/poll.html
146-
[kotlinx.coroutines.channels.ReceiveChannel.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive-or-null.html
147-
[kotlinx.coroutines.channels.ReceiveChannel.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/on-receive-or-null.html
146+
[kotlinx.coroutines.channels.receiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/receive-or-null.html
147+
[kotlinx.coroutines.channels.onReceiveOrNull]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/on-receive-or-null.html
148148
<!--- INDEX kotlinx.coroutines.selects -->
149149
[kotlinx.coroutines.selects.select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
150150
[kotlinx.coroutines.selects.SelectBuilder.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/-select-builder/on-timeout.html

0 commit comments

Comments
 (0)