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.
Related
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
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()
The following two method contain the same functionality, the only difference is that one is suspendable and the other isn't (same for parameters).
How can I extract the implementation and reuse it for both functions?
fun validationWrapper(supplier: () -> Unit) = try {
supplier.invoke()
} catch (ex: Exception) {
when (ex) {
is IllegalArgumentException, is IllegalStateException -> throw ValidationException(ex.message!!)
else -> throw ex
}
}
suspend fun validationWrapper(supplier: suspend () -> Unit) = try {
supplier.invoke()
} catch (ex: Exception) {
when (ex) {
is IllegalArgumentException, is IllegalStateException -> throw ValidationException(ex.message!!)
else -> throw ex
}
}
I could keep only the suspendable function but that would mean I should use a runBlocking each time I use it.
Keep the non-suspend version and make it inline. This solves the problem because the inlined lambda can then contain suspending calls without being declared suspend itself. It is how most of the Kotlin stdlib does this (forEach, map, etc.):
inline fun validationWrapper(supplier: () -> Unit) = try {
supplier.invoke()
} catch (ex: Exception) {
when (ex) {
is IllegalArgumentException, is IllegalStateException -> throw ValidationException(ex.message!!)
else -> throw ex
}
}
Also, it is generally useful for higher-order functions that take lambdas to be declared inline, because it avoids extra costs of lambda instances.
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()
}
Is this setup somehow possible?
scopeA.launch {
try {
scopeB.launch {
throw SomeException()
}
} catch (e: SomeException) {
// Want to catch here
}
}
You seem to wish for contradictory things: both launch a concurrent coroutine and suspend the current work until it completes, catching possible exceptions. You must decide what you actually want:
If you want concurrency, then handle the exception within the child coroutine:
scopeA.launch {
launch(dispatcherB) {
try {
throw SomeException()
} catch (e: SomeException) {
// handle
}
}
}
If you don't want it, don't launch a coroutine but use withContext(dispatcherB) instead:
scopeA.launch {
try {
withContext(dispatcherB) {
throw SomeException()
}
} catch (e: SomeException) {
// handle
}
}
If you literally want two coroutines with decoupled lifecycles to communicate, you must introduce a decoupled communication mechanism as well. For example, you can set up a Channel<SomeException> or a Flow<SomeException>.