@@ -11,9 +11,9 @@ import kotlinx.coroutines.*
11
11
import kotlinx.coroutines.channels.*
12
12
import kotlinx.coroutines.channels.Channel.Factory.BUFFERED
13
13
import kotlinx.coroutines.flow.internal.*
14
- import kotlinx.coroutines.flow.internal.unsafeFlow as flow
15
14
import kotlin.coroutines.*
16
15
import kotlin.jvm.*
16
+ import kotlinx.coroutines.flow.internal.unsafeFlow as flow
17
17
18
18
/* *
19
19
* Creates a flow from the given suspendable [block].
@@ -259,10 +259,14 @@ public fun <T> channelFlow(@BuilderInference block: suspend ProducerScope<T>.()
259
259
*
260
260
* This builder ensures thread-safety and context preservation, thus the provided [ProducerScope] can be used
261
261
* from any context, e.g. from a callback-based API.
262
- * The resulting flow completes as soon as the code in the [block] and all its children completes.
263
- * Use [awaitClose] as the last statement to keep it running.
264
- * The [awaitClose] argument is called either when a flow consumer cancels the flow collection
265
- * or when a callback-based API invokes [SendChannel.close] manually.
262
+ * The resulting flow completes as soon as the code in the [block] completes.
263
+ * [awaitClose] should be used to keep the flow running, otherwise the channel will be closed immediately
264
+ * when block completes.
265
+ * [awaitClose] argument is called either when a flow consumer cancels the flow collection
266
+ * or when a callback-based API invokes [SendChannel.close] manually and is typically used
267
+ * to cleanup the resources after the completion, e.g. unregister a callback.
268
+ * Using [awaitClose] is mandatory in order to prevent memory leaks when the flow collection is cancelled,
269
+ * otherwise the callback may keep running even when the flow collector is already completed.
266
270
*
267
271
* A channel with the [default][Channel.BUFFERED] buffer size is used. Use the [buffer] operator on the
268
272
* resulting flow to specify a user-defined value and to control what happens when data is produced faster
@@ -287,21 +291,20 @@ public fun <T> channelFlow(@BuilderInference block: suspend ProducerScope<T>.()
287
291
* override fun onCompleted() = channel.close()
288
292
* }
289
293
* api.register(callback)
290
- * // Suspend until either onCompleted or external cancellation are invoked
294
+ * /*
295
+ * * Suspends until either 'onCompleted' from the callback is invoked
296
+ * * or flow collector is cancelled (e.g. by 'take(1)' or because a collector's activity was destroyed).
297
+ * * In both cases, callback will be properly unregistered.
298
+ * */
291
299
* awaitClose { api.unregister(callback) }
292
300
* }
293
301
* ```
294
- *
295
- * This function is an alias for [channelFlow], it has a separate name to reflect
296
- * the intent of the usage (integration with a callback-based API) better.
297
302
*/
298
- @Suppress(" NOTHING_TO_INLINE" )
299
303
@ExperimentalCoroutinesApi
300
- public inline fun <T > callbackFlow (@BuilderInference noinline block : suspend ProducerScope <T >.() -> Unit ): Flow <T > =
301
- channelFlow(block)
304
+ public fun <T > callbackFlow (@BuilderInference block : suspend ProducerScope <T >.() -> Unit ): Flow <T > = CallbackFlowBuilder (block)
302
305
303
306
// ChannelFlow implementation that is the first in the chain of flow operations and introduces (builds) a flow
304
- private class ChannelFlowBuilder <T >(
307
+ private open class ChannelFlowBuilder <T >(
305
308
private val block : suspend ProducerScope <T >.() -> Unit ,
306
309
context : CoroutineContext = EmptyCoroutineContext ,
307
310
capacity : Int = BUFFERED
@@ -315,3 +318,36 @@ private class ChannelFlowBuilder<T>(
315
318
override fun toString (): String =
316
319
" block[$block ] -> ${super .toString()} "
317
320
}
321
+
322
+ private class CallbackFlowBuilder <T >(
323
+ private val block : suspend ProducerScope <T >.() -> Unit ,
324
+ context : CoroutineContext = EmptyCoroutineContext ,
325
+ capacity : Int = BUFFERED
326
+ ) : ChannelFlowBuilder<T>(block, context, capacity) {
327
+
328
+ private val collectCallback: suspend (ProducerScope <T >) -> Unit = {
329
+ collectTo(it)
330
+ /*
331
+ * We expect user either call `awaitClose` from within a block (then the channel is closed at this moment)
332
+ * or being closed/cancelled externally/manually. Otherwise "user forgot to call
333
+ * awaitClose and receives unhelpful ClosedSendChannelException exceptions" situation is detected.
334
+ */
335
+ if (it.isActive && ! it.isClosedForSend) {
336
+ throw IllegalStateException (
337
+ """
338
+ 'awaitClose { yourCallbackOrListener.cancel() }' should be used in the end of callbackFlow block.
339
+ Otherwise, a callback/listener may leak in case of cancellation external cancellation (e.g. by 'take(1)' or destroyed activity).
340
+ For a more detailed explanation, please refer to callbackFlow KDoc.
341
+ """ .trimIndent())
342
+ }
343
+ }
344
+
345
+ override fun broadcastImpl (scope : CoroutineScope , start : CoroutineStart ): BroadcastChannel <T > =
346
+ scope.broadcast(context, produceCapacity, start, block = collectCallback)
347
+
348
+ override fun produceImpl (scope : CoroutineScope ): ReceiveChannel <T > =
349
+ scope.produce(context, produceCapacity, block = collectCallback)
350
+
351
+ override fun create (context : CoroutineContext , capacity : Int ): ChannelFlow <T > =
352
+ CallbackFlowBuilder (block, context, capacity)
353
+ }
0 commit comments