Skip to content

Commit 3275d22

Browse files
authored
Propagate exception from stateIn to the caller when the upstream failed to produce initial value (#2329)
1 parent 6843648 commit 3275d22

File tree

2 files changed

+30
-12
lines changed

2 files changed

+30
-12
lines changed

kotlinx-coroutines-core/common/src/flow/operators/Share.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -330,13 +330,20 @@ private fun <T> CoroutineScope.launchSharingDeferred(
330330
result: CompletableDeferred<StateFlow<T>>
331331
) {
332332
launch(context) {
333-
var state: MutableStateFlow<T>? = null
334-
upstream.collect { value ->
335-
state?.let { it.value = value } ?: run {
336-
state = MutableStateFlow(value).also {
337-
result.complete(it.asStateFlow())
333+
try {
334+
var state: MutableStateFlow<T>? = null
335+
upstream.collect { value ->
336+
state?.let { it.value = value } ?: run {
337+
state = MutableStateFlow(value).also {
338+
result.complete(it.asStateFlow())
339+
}
338340
}
339341
}
342+
} catch (e: Throwable) {
343+
// Notify the waiter that the flow has failed
344+
result.completeExceptionally(e)
345+
// But still cancel the scope where state was (not) produced
346+
throw e
340347
}
341348
}
342349
}

kotlinx-coroutines-core/common/test/flow/sharing/StateInTest.kt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,21 @@ class StateInTest : TestBase() {
3030

3131
@Test
3232
fun testUpstreamCompletedNoInitialValue() =
33-
testUpstreamCompletedOrFailedReset(failed = false, iv = false)
33+
testUpstreamCompletedOrFailedReset(failed = false, withInitialValue = false)
3434

3535
@Test
3636
fun testUpstreamFailedNoInitialValue() =
37-
testUpstreamCompletedOrFailedReset(failed = true, iv = false)
37+
testUpstreamCompletedOrFailedReset(failed = true, withInitialValue = false)
3838

3939
@Test
4040
fun testUpstreamCompletedWithInitialValue() =
41-
testUpstreamCompletedOrFailedReset(failed = false, iv = true)
41+
testUpstreamCompletedOrFailedReset(failed = false, withInitialValue = true)
4242

4343
@Test
4444
fun testUpstreamFailedWithInitialValue() =
45-
testUpstreamCompletedOrFailedReset(failed = true, iv = true)
45+
testUpstreamCompletedOrFailedReset(failed = true, withInitialValue = true)
4646

47-
private fun testUpstreamCompletedOrFailedReset(failed: Boolean, iv: Boolean) = runTest {
47+
private fun testUpstreamCompletedOrFailedReset(failed: Boolean, withInitialValue: Boolean) = runTest {
4848
val emitted = Job()
4949
val terminate = Job()
5050
val sharingJob = CompletableDeferred<Unit>()
@@ -56,7 +56,7 @@ class StateInTest : TestBase() {
5656
}
5757
val scope = this + sharingJob
5858
val shared: StateFlow<String?>
59-
if (iv) {
59+
if (withInitialValue) {
6060
shared = upstream.stateIn(scope, SharingStarted.Eagerly, null)
6161
assertEquals(null, shared.value)
6262
} else {
@@ -75,4 +75,15 @@ class StateInTest : TestBase() {
7575
assertNull(sharingJob.getCompletionExceptionOrNull())
7676
}
7777
}
78-
}
78+
79+
@Test
80+
fun testUpstreamFailedIMmediatelyWithInitialValue() = runTest {
81+
val ceh = CoroutineExceptionHandler { _, _ -> expect(2) }
82+
val flow = flow<Int> {
83+
expect(1)
84+
throw TestException()
85+
}
86+
assertFailsWith<TestException> { flow.stateIn(CoroutineScope(ceh)) }
87+
finish(3)
88+
}
89+
}

0 commit comments

Comments
 (0)