commonMain.flow.internal.Merge.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlinx-coroutines-core-linuxx64 Show documentation
Show all versions of kotlinx-coroutines-core-linuxx64 Show documentation
Coroutines support libraries for Kotlin
/*
* Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.coroutines.flow.internal
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.*
import kotlin.coroutines.*
internal class ChannelFlowTransformLatest(
private val transform: suspend FlowCollector.(value: T) -> Unit,
flow: Flow,
context: CoroutineContext = EmptyCoroutineContext,
capacity: Int = Channel.BUFFERED
) : ChannelFlowOperator(flow, context, capacity) {
override fun create(context: CoroutineContext, capacity: Int): ChannelFlow =
ChannelFlowTransformLatest(transform, flow, context, capacity)
override suspend fun flowCollect(collector: FlowCollector) {
assert { collector is SendingCollector } // So cancellation behaviour is not leaking into the downstream
flowScope {
var previousFlow: Job? = null
flow.collect { value ->
previousFlow?.apply {
cancel(ChildCancelledException())
join()
}
// Do not pay for dispatch here, it's never necessary
previousFlow = launch(start = CoroutineStart.UNDISPATCHED) {
collector.transform(value)
}
}
}
}
}
internal class ChannelFlowMerge(
flow: Flow>,
private val concurrency: Int,
context: CoroutineContext = EmptyCoroutineContext,
capacity: Int = Channel.OPTIONAL_CHANNEL
) : ChannelFlowOperator, T>(flow, context, capacity) {
override fun create(context: CoroutineContext, capacity: Int): ChannelFlow =
ChannelFlowMerge(flow, concurrency, context, capacity)
// The actual merge implementation with concurrency limit
private suspend fun mergeImpl(scope: CoroutineScope, collector: ConcurrentFlowCollector) {
val semaphore = Semaphore(concurrency)
val job: Job? = coroutineContext[Job]
flow.collect { inner ->
/*
* We launch a coroutine on each emitted element and the only potential
* suspension point in this collector is `semaphore.acquire` that rarely suspends,
* so we manually check for cancellation to propagate it to the upstream in time.
*/
job?.ensureActive()
semaphore.acquire()
scope.launch {
try {
inner.collect(collector)
} finally {
semaphore.release() // Release concurrency permit
}
}
}
}
// Fast path in ChannelFlowOperator calls this function (channel was not created yet)
override suspend fun flowCollect(collector: FlowCollector) {
// this function should not have been invoked when channel was explicitly requested
assert { capacity == Channel.OPTIONAL_CHANNEL }
flowScope {
mergeImpl(this, collector.asConcurrentFlowCollector())
}
}
// Slow path when output channel is required (and was created)
override suspend fun collectTo(scope: ProducerScope) =
mergeImpl(scope, SendingCollector(scope))
override fun additionalToStringProps(): String =
"concurrency=$concurrency, "
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy