kotlin coroutineScope exception cannot be handled - kotlin

Hi I'm playing around kotlin and I want to catch and throw our the exception, so my code is like this
runBlocking {
coroutineScope {
nonNullRecords.forEach {
launch(Dispatchers.IO) {
val time = measureTimeMillis {
try {
process(it)
} catch (e: Exception) {
throw Exception(e)
}
}
}
}
}
}
the process function is s suspend function.
So the thing is that in this case, if the process function has an exception(which is HttpTimeoutException), my service will crash which is excepted. But if I don't use the try catch, then my service will ignore the exception.
and further more, if I use throw e instead of throw Exception(e), it will also ignore the exception
May I know how this works? thanks so much

This is because Ktor's HttpRequestTimeoutException extends CancellationException, which is an exception used internally by coroutines to implement the cancellation mechanism. This is why this particular exception is not considered a real problem and is silently ignored.
This was fixed and will be released in Ktor 2.0.0:
https://youtrack.jetbrains.com/issue/KTOR-3192

Related

Catch Exception and prevent it from propogating to parent scope in Kotlin Coroutines

Background: I am fetching data from Bluetooth and after every packet is received it is processed. What I am trying to do is to start timeout when data processing finishes and stop the timer when a new packet is received.
Tried creating a timeout logic using Flow. I created a short snippet to test if it works:
class ExceptionPropagationTest {
#Test
fun test()= runBlocking {
println(get(coroutineContext))
}
suspend fun get(coroutineContext: CoroutineContext) = withContext(coroutineContext) {
try {
enableDataTransferTimeout()
delay(3000)
"Result"
} catch (e: IllegalStateException) {
println("Exception caught ${System.currentTimeMillis()}")
"No Result"
}
}
private fun CoroutineScope.enableDataTransferTimeout() {
flowOf("1").onEach {
delay(500)
doSomething()
throw IllegalStateException()
}.launchIn(this)
}
private suspend fun doSomething(){
// Do some suspending work
}
}
Above code first prints:
Exception caught [CURRENT_TIME]
Then logs exceptions stack trace and crashes:
java.lang.IllegalStateException at
com.app.ExceptionPropagationTest$enableDataTransferTimeout$1.invokeSuspend(ExceptionPropagationTest.kt:49)
(Coroutine boundary) at
com.app.ExceptionPropagationTest$test$1.invokeSuspend(ExceptionPropagationTest.kt:32)
Caused by: java.lang.IllegalStateException
Question: Is there any way to catch the exception and return value without propagating the exception to parent scope?
If it is not possible with flow any other solution or suggestion is welcome.
You can use the catch method. Docs here
flowOf("1")
.map {
delay(500)
doSomething()
throw IllegalStateException()
}
.catch { ... } // catches exceptions in map or other operands you applied
.collect()

Why does changing dispatcher in catch block throw VerifyError

I want to understand why this exception is thrown when I use withContext() with Dispatchers.Main or Dispatchers.IO in catch block.
Here's my code:
init {
viewModelScope.launch {
try {
throw RuntimeException("whatever")
} catch (e: Exception){
withContext(Dispatchers.MAIN) {
e.printStackTrace()
}
}
}
}
This code throws java.lang.VerifyError
Verifier rejected class xx.xxxx.ErrorViewModel: java.lang.Object
xx.xxxx.ErrorViewModel$1.invokeSuspend(java.lang.Object)
failed to verify: java.lang.Object
xx.xxxx.ErrorViewModel$1.invokeSuspend(java.lang.Object):
[0x3D] register v4 has type Reference: java.lang.Exception but expected
Precise Reference: kotlin.jvm.internal.Ref$ObjectRef
(declaration of 'xx.xxxx.ErrorViewModel$1' appears in
/data/app/xx.xxxx-9pkI5L5NB9qa1CWUxAapUw==/base.apk!classes2.dex)
You should never see a VerifyError; this probably indicates a bug in the compiler.
I'd suggest raising an issue on the JetBrains YouTrack site.

Stop library from swallowing all exceptions in Kotlin

I'm writing an app in Kotlin that uses a third party library which does some asynchronous work, and then passes the result back to my code in a callback. The problem is that the library wraps the callback in a generic try-catch block so any exceptions my code then throws is swallowed by the library.
Is there a way to catch my exceptions without changing to a different thread? I've tried wrapping my code in a runBlocking and a withContext but exceptions are still caught by the library
You should try to handle exceptions yourself in the code block which is passed to the library, then you can return them as a callback result.
Something like this:
fun <T> libraryCall(block: () -> Result<T>): Result<T> {
TODO()
}
sealed class Result<out T> {
class Data<T>(val data: T) : Result<T>()
class Error(val ex: Exception) : Result<Nothing>()
}
fun main() {
val result = libraryCall {
return#libraryCall try {
Result.Data(5)
} catch (e: Exception) {
Result.Error(e)
}
}
}
The best solution I found is to use a CoroutineExceptionHandler.
val handler = CoroutineExceptionHandler { _, exception ->
// This will crash the app rather than have the library swallow it
throw exception
}
// Callback on some thread
GlobalScope.launch(Dispatchers.IO) {
GlobalScope.launch(handler) {
throw RuntimeException("My code has thrown an exception")
}.join()
}

Should I handle exception with Closable.use{...} in Kotlin?

According to the source of Closable.use, if an error occurs, an exception will be thrown.
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
when {
apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
this == null -> {}
exception == null -> close()
else ->
try {
close()
} catch (closeException: Throwable) {
// cause.addSuppressed(closeException) // ignored here
}
}
}
In most examples of Closable.use, try-catch is not used as shown below.
Why isn't error handling needed? Is it safe?
BufferedReader(FileReader("test.file")).use { return it.readLine() }
This line
BufferedReader(FileReader("test.file")).use { return it.readLine() }
is not safe. Reading and closing the reader can both throw IOExceptions, which are not RuntimeExceptions (caused by programming errors). That means leaving them uncaught exposes your app to crashing from things outside your control.
Since Kotlin doesn't have checked exceptions, the compiler won't warn you about this. To do this safely, you need to wrap it in try/catch. And if you want to handle read errors differently than close errors, you either need to have inner and outer try/catch statements:
try {
BufferedReader(FileReader("test.file")).use {
try {
return it.readLine()
catch (e: IOException) {
println("Failed to read line")
}
}
} catch (e: IOException) {
println("Failed to close reader")
}
or wrap the whole thing and extract any suppressed exceptions, but then its cumbersome to distinguish between them:
try {
BufferedReader(FileReader("test.file")).use { return it.readLine() }
} catch (e: IOException) {
val throwables = listOf(e, *e.suppressed)
for (throwable in throwables)
println(throwable.message)
}
But in practice, you're probably not going to react differently to various IOExceptions, so you can just put the one try/catch outside.
We see from Kotlin documentation what is the purpose of the use function:
Executes the given block function on this resource and then closes it
down correctly whether an exception is thrown or not.
This function closes the resource properly if the block function completed successfully or threw an exception. It is your responsibility to handle the result of the block function.
If an exception was thrown and there is a way to handle it and proceed with code execution, use a try/catch. If there is nothing to do about it and control should be passed to the caller, it is not necessary to use a try/catch.

When you throw an exception in a coroutine scope, is the coroutine scope reusable?

I've been having problems figuring out error handling with coroutines that I've narrowed down to this unit test with the following steps:
I create a coroutine scope, with any dispatcher.
I throw an exception anywhere within this scope in an async block (or even in a nested async block).
I call await on the returned deferred value and handle the exception.
This is all fine. However, when I try to use the same coroutine scope to launch a new coroutine, this always completes exceptionally with the same exception.
Here is the test:
fun `when you throw an exception in a coroutine scope, is the coroutine scope dead?`() {
val parentJob = Job()
val coroutineScope = CoroutineScope(parentJob + Dispatchers.Default)
val deferredResult = coroutineScope.async { throw IllegalStateException() }
runBlocking {
try {
deferredResult.await()
} catch (e: IllegalStateException) {
println("We caught the exception. Good.")
}
try {
coroutineScope.async { println("we can still use the scope") }.await()
} catch (e: IllegalStateException) {
println("Why is this same exception still being thrown?")
}
}
}
Here is the output of the test:
We caught the exception. Good.
Why is this same exception still being thrown?
Why is this happening?
My understanding was that you could handle exceptions normally and recover from them with coroutines.
How should I deal with exceptions?
Do I need to create a new coroutineScope?
Can I never throw exceptions if I want to keep using the same coroutineScope?
Should I return Either<Result, Exception>?
I've tried using CoroutineExceptionHandler but I still get the same results.
Note I'm using Kotlin 1.3
When you start a coroutine in a scope (using either async or launch), then a failure of a coroutine by default cancels this scope to promptly cancel all the other children. This design avoid dangling and lost exceptions.
The general advice here is:
Don't use async/await unless you really need concurrency. When you design your code with suspending functions there is no much need to use async and await.
If you do need concurrent execution, then follow the pattern:
coroutineScope {
val d1 = async { doOne() }
val d2 = async { doTwo() }
...
// retrieve and process results
process(d1.await(), d2.await(), .... )
}
If you need to handle a failure of a concurrent operation, then put try { ... } catch { ... } around coroutineScope { ... } to catch a failure in any of the concurrently executing operations.
There are additional advanced mechanisms (like SupervisorJob) that allow fine-grained exception handling. You can read more in the documentation https://kotlinlang.org/docs/reference/coroutines/exception-handling.html