@@ -10,27 +10,48 @@ import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
10
10
import kotlinx.coroutines.intrinsics.*
11
11
import kotlin.coroutines.*
12
12
import kotlin.coroutines.intrinsics.*
13
+ import kotlin.native.concurrent.*
13
14
14
15
/* *
15
16
* Broadcasts all elements of the channel.
17
+ * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel].
16
18
*
17
19
* The kind of the resulting channel depends on the specified [capacity] parameter:
18
20
* when `capacity` is positive (1 by default), but less than [UNLIMITED] -- uses `ArrayBroadcastChannel` with a buffer of given capacity,
19
21
* when `capacity` is [CONFLATED] -- uses [ConflatedBroadcastChannel] that conflates back-to-back sends;
20
22
* Note that resulting channel behaves like [ConflatedBroadcastChannel] but is not an instance of [ConflatedBroadcastChannel].
21
23
* otherwise -- throws [IllegalArgumentException].
22
24
*
25
+ * ### Cancelling broadcast
26
+ *
27
+ * **To stop broadcasting from the underlying channel call [cancel][BroadcastChannel.cancel] on the result.**
28
+ *
29
+ * Do not use [close][BroadcastChannel.close] on the resulting channel.
30
+ * It causes eventual failure of the broadcast coroutine and cancellation of the underlying channel, too,
31
+ * but it is not as prompt.
32
+ *
33
+ * ### Future replacement
34
+ *
35
+ * This function has an inappropriate result type of [BroadcastChannel] which provides
36
+ * [send][BroadcastChannel.send] and [close][BroadcastChannel.close] operations that interfere with
37
+ * the broadcasting coroutine in hard-to-specify ways. It will be replaced with
38
+ * sharing operators on [Flow][kotlinx.coroutines.flow.Flow] in the future.
39
+ *
23
40
* @param start coroutine start option. The default value is [CoroutineStart.LAZY].
24
41
*/
25
42
fun <E > ReceiveChannel<E>.broadcast (
26
43
capacity : Int = 1,
27
44
start : CoroutineStart = CoroutineStart .LAZY
28
- ): BroadcastChannel <E > =
29
- GlobalScope .broadcast(Dispatchers .Unconfined , capacity = capacity, start = start, onCompletion = consumes()) {
45
+ ): BroadcastChannel <E > {
46
+ val scope = GlobalScope + Dispatchers .Unconfined + CoroutineExceptionHandler { _, _ -> }
47
+ // We can run this coroutine in the context that ignores all exceptions, because of `onCompletion = consume()`
48
+ // which passes all exceptions upstream to the source ReceiveChannel
49
+ return scope.broadcast(capacity = capacity, start = start, onCompletion = consumes()) {
30
50
for (e in this @broadcast) {
31
51
send(e)
32
52
}
33
53
}
54
+ }
34
55
35
56
/* *
36
57
* Launches new coroutine to produce a stream of values by sending them to a broadcast channel
@@ -63,6 +84,21 @@ fun <E> ReceiveChannel<E>.broadcast(
63
84
*
64
85
* See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine.
65
86
*
87
+ * ### Cancelling broadcast
88
+ *
89
+ * **To stop broadcasting from the underlying channel call [cancel][BroadcastChannel.cancel] on the result.**
90
+ *
91
+ * Do not use [close][BroadcastChannel.close] on the resulting channel.
92
+ * It causes failure of the `send` operation in broadcast coroutine and would not cancel it if the
93
+ * coroutine is doing something else.
94
+ *
95
+ * ### Future replacement
96
+ *
97
+ * This function has an inappropriate result type of [BroadcastChannel] which provides
98
+ * [send][BroadcastChannel.send] and [close][BroadcastChannel.close] operations that interfere with
99
+ * the broadcasting coroutine in hard-to-specify ways. It will be replaced with
100
+ * sharing operators on [Flow][kotlinx.coroutines.flow.Flow] in the future.
101
+ *
66
102
* @param context additional to [CoroutineScope.coroutineContext] context of the coroutine.
67
103
* @param capacity capacity of the channel's buffer (1 by default).
68
104
* @param start coroutine start option. The default value is [CoroutineStart.LAZY].
@@ -107,8 +143,9 @@ private open class BroadcastCoroutine<E>(
107
143
}
108
144
109
145
override fun cancelInternal (cause : Throwable ) {
110
- _channel .cancel(cause.toCancellationException()) // cancel the channel
111
- cancelCoroutine(cause) // cancel the job
146
+ val exception = cause.toCancellationException()
147
+ _channel .cancel(exception) // cancel the channel
148
+ cancelCoroutine(exception) // cancel the job
112
149
}
113
150
114
151
override fun onCompleted (value : Unit ) {
@@ -119,6 +156,13 @@ private open class BroadcastCoroutine<E>(
119
156
val processed = _channel .close(cause)
120
157
if (! processed && ! handled) handleCoroutineException(context, cause)
121
158
}
159
+
160
+ // The BroadcastChannel could be also closed
161
+ override fun close (cause : Throwable ? ): Boolean {
162
+ val result = _channel .close(cause)
163
+ start() // start coroutine if it was not started yet
164
+ return result
165
+ }
122
166
}
123
167
124
168
private class LazyBroadcastCoroutine <E >(
0 commit comments