Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve exception transparency: explicitly allow throwing exceptions … (
#3017) * Improve exception transparency: explicitly allow throwing exceptions from the upstream when the downstream has been failed, suppress the downstream exception by the new one, but still ignore it in the exception handling operators, that still consider the flow failed. It solves the problem of graceful shutdown: when the upstream fails unwillingly (e.g. `file.close()` has thrown in `finally` block), we cannot treat it as an exception transparency violation (hint: `finally` is a shortcut for `catch` + body that rethrows in the end), but we also cannot leave things as is, otherwise, it leads to unforeseen consequences such as successful `retry` and `catch` operators that may, or may not, then fail with an exception on an attempt to emit. Upstream exception supersedes the downstream exception only if it is not an instance of `CancellationException`, semantically emulating cancellation-friendly 'use' block. Fixes #2860
- Loading branch information
Showing
5 changed files
with
240 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
kotlinx-coroutines-core/jvm/test/exceptions/FlowSuppressionTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. | ||
*/ | ||
|
||
package kotlinx.coroutines.exceptions | ||
|
||
import kotlinx.coroutines.* | ||
import kotlinx.coroutines.flow.* | ||
import org.junit.* | ||
import org.junit.Test | ||
import kotlin.test.* | ||
|
||
class FlowSuppressionTest : TestBase() { | ||
@Test | ||
fun testSuppressionForPrimaryException() = runTest { | ||
val flow = flow { | ||
try { | ||
emit(1) | ||
} finally { | ||
throw TestException() | ||
} | ||
}.catch { expectUnreached() }.onEach { throw TestException2() } | ||
|
||
try { | ||
flow.collect() | ||
} catch (e: Throwable) { | ||
assertIs<TestException>(e) | ||
assertIs<TestException2>(e.suppressed[0]) | ||
} | ||
} | ||
|
||
@Test | ||
fun testSuppressionForPrimaryExceptionRetry() = runTest { | ||
val flow = flow { | ||
try { | ||
emit(1) | ||
} finally { | ||
throw TestException() | ||
} | ||
}.retry { expectUnreached(); true }.onEach { throw TestException2() } | ||
|
||
try { | ||
flow.collect() | ||
} catch (e: Throwable) { | ||
assertIs<TestException>(e) | ||
assertIs<TestException2>(e.suppressed[0]) | ||
|
||
} | ||
} | ||
|
||
@Test | ||
fun testCancellationSuppression() = runTest { | ||
val flow = flow { | ||
try { | ||
expect(1) | ||
emit(1) | ||
} finally { | ||
expect(3) | ||
throw CancellationException("") | ||
} | ||
}.catch { expectUnreached() }.onEach { | ||
expect(2) | ||
throw TestException("") | ||
} | ||
|
||
try { | ||
flow.collect() | ||
} catch (e: Throwable) { | ||
assertIs<TestException>(e) | ||
assertIs<CancellationException>(e.suppressed[0]) | ||
} | ||
finish(4) | ||
} | ||
} |