@@ -11,8 +11,10 @@ import kotlinx.coroutines.flow.*
11
11
import kotlinx.coroutines.internal.*
12
12
import kotlin.coroutines.*
13
13
import kotlin.coroutines.intrinsics.*
14
+ import kotlin.jvm.*
15
+ import kotlin.math.*
14
16
15
- internal fun getNull (): Symbol = NULL // Workaround for JS BE bug
17
+ private class Update (@JvmField val index : Int , @JvmField val value : Any? )
16
18
17
19
@PublishedApi
18
20
internal suspend fun <R , T > FlowCollector<R>.combineInternal (
@@ -22,27 +24,17 @@ internal suspend fun <R, T> FlowCollector<R>.combineInternal(
22
24
): Unit = flowScope { // flow scope so any cancellation within the source flow will cancel the whole scope
23
25
val size = flows.size
24
26
if (size == 0 ) return @flowScope // bail-out for empty input
25
- val latestValues = Array <Any ?>(size) { getNull() }
27
+ val latestValues = Array <Any ?>(size) { UNINITIALIZED }
26
28
val isClosed = Array (size) { false }
27
- val resultChannel = Channel <Array < T >>( Channel . CONFLATED )
29
+ val resultChannel = Channel <Update >(flows.size )
28
30
val nonClosed = LocalAtomicInt (size)
29
- val remainingAbsentValues = LocalAtomicInt ( size)
31
+ var remainingAbsentValues = size
30
32
for (i in 0 until size) {
31
33
// Coroutine per flow that keeps track of its value and sends result to downstream
32
34
launch {
33
35
try {
34
36
flows[i].collect { value ->
35
- val previous = latestValues[i]
36
- latestValues[i] = value
37
- if (previous == = getNull()) remainingAbsentValues.decrementAndGet()
38
- if (remainingAbsentValues.value == 0 ) {
39
- val results = arrayFactory()
40
- for (index in 0 until size) {
41
- results[index] = getNull().unbox(latestValues[index])
42
- }
43
- // NB: here actually "stale" array can overwrite a fresh one and break linearizability
44
- resultChannel.send(results as Array <T >)
45
- }
37
+ resultChannel.send(Update (i, value))
46
38
yield () // Emulate fairness for backward compatibility
47
39
}
48
40
} finally {
@@ -55,8 +47,56 @@ internal suspend fun <R, T> FlowCollector<R>.combineInternal(
55
47
}
56
48
}
57
49
50
+ // val lastReceivedEpoch = IntArray(size)
51
+ // var currentEpoch = 0
52
+ // while (!resultChannel.isClosedForReceive) {
53
+ // ++currentEpoch
54
+ // var shouldSuspend = true
55
+ // // Start batch
56
+ // var elementsReceived = 0
57
+ // while (true) {
58
+ // // The very first receive in epoch should be suspending
59
+ // val element = if (shouldSuspend) {
60
+ // shouldSuspend = false
61
+ // resultChannel.receiveOrNull()
62
+ // } else {
63
+ // resultChannel.poll()
64
+ // }
65
+ // if (element === null) break // End batch processing, nothing to receive
66
+ // ++elementsReceived
67
+ // val index = element.index
68
+ // // Update valued
69
+ // val previous = latestValues[index]
70
+ // latestValues[index] = element.value
71
+ // if (previous === UNINITIALIZED) --remainingAbsentValues
72
+ // // Check epoch
73
+ // // Received the second value from the same flow in the same epoch -- bail out
74
+ // if (lastReceivedEpoch[index] == currentEpoch) break
75
+ // lastReceivedEpoch[index] = currentEpoch
76
+ // }
77
+ //
78
+ // // Process batch result
79
+ // if (remainingAbsentValues == 0 && elementsReceived != 0) {
80
+ // val results = arrayFactory()
81
+ // for (i in 0 until size) {
82
+ // results[i] = latestValues[i] as T?
83
+ // }
84
+ // transform(results as Array<T>)
85
+ // }
86
+ // }
87
+
58
88
resultChannel.consumeEach {
59
- transform(it)
89
+ val index = it.index
90
+ val previous = latestValues[index]
91
+ latestValues[index] = it.value
92
+ if (previous == = UNINITIALIZED ) -- remainingAbsentValues
93
+ if (remainingAbsentValues == 0 ) {
94
+ val results = arrayFactory()
95
+ for (i in 0 until size) {
96
+ results[i] = latestValues[i] as T ?
97
+ }
98
+ transform(results as Array <T >)
99
+ }
60
100
}
61
101
}
62
102
@@ -101,7 +141,7 @@ internal fun <T1, T2, R> zipImpl(flow: Flow<T1>, flow2: Flow<T2>, transform: sus
101
141
flow.collect { value ->
102
142
withContextUndispatched(newContext, cnt) {
103
143
val otherValue = second.receiveOrNull() ? : throw AbortFlowException (this @unsafeFlow)
104
- emit(transform(getNull() .unbox(value), getNull() .unbox(otherValue)))
144
+ emit(transform(NULL .unbox(value), NULL .unbox(otherValue)))
105
145
}
106
146
}
107
147
}
@@ -129,6 +169,6 @@ private suspend fun withContextUndispatched(
129
169
// Channel has any type due to onReceiveOrNull. This will be fixed after receiveOrClosed
130
170
private fun CoroutineScope.asChannel (flow : Flow <* >): ReceiveChannel <Any > = produce {
131
171
flow.collect { value ->
132
- return @collect channel.send(value ? : getNull() )
172
+ return @collect channel.send(value ? : NULL )
133
173
}
134
174
}
0 commit comments