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.
Related
I want to call an API for each element in a list.
So I created below code which is an extension function:
suspend fun <T, V> Iterable<T>.customAsyncAll(method: suspend (T) -> V): Iterable<V> {
val deferredList = mutableListOf<Deferred<V>>()
val scope = CoroutineScope(dispatchers.io)
forEach {
val deferred = scope.async {
try {
method(it)
} catch (e: Exception) {
log.error { "customAsyncAll Exception in $method method " + e.stackTraceToString())
}
throw e
}
}
deferredList.add(deferred)
}
return deferredList.awaitAll()
}
Call the code as:
val result = runBlocking{ list.customAsyncAll { apiCall(it) }.toList() }
I see error posting Resource Exhausted event: Java heap space. What is wrong with this code?
When an exception is thrown in one of the api calls, will the rest of the courouting async stuff be released or it still occupies heap space?
I'm guessing you are passing a somewhat large list (50+ items). I do believe that making so many calls is the problem, and realistically speaking I don't think you will have any performance gain by opening more than 10 connections to the API at a time. Μy suggestion would be to limit the concurrent calls to any number of less than 20.
There are many ways to implement this, using Semaphore is my recommendation.
suspend fun <T, V> Iterable<T>.customAsyncAll(method: suspend (T) -> V): Iterable<V> {
val deferredList = mutableListOf<Deferred<V>>()
val scope = CoroutineScope(Dispatchers.IO)
val sema = Semaphore(10)
forEach {
val deferred = scope.async {
sema.withPermit {
try {
method(it)
} catch (e: Exception) {
log.error {
"customAsyncAll Exception in $method method "
+ e.stackTraceToString())
}
throw e
}
}
}
deferredList.add(deferred)
}
return deferredList.awaitAll()
}
sidenote
Be sure to cancel any custom CouroutineScope you create after you are done with it, see Custom usage.
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()
I have a suspend function
private suspend fun getResponse(record: String): HashMap<String, String> {}
When I call it in my main function I'm doing this, but the type of response is Job, not HashMap, how can I get the correct return type?
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
event?.records?.forEach {
try {
val response: Job = GlobalScope.launch {
getResponse(it.body)
}
} catch (ex: Exception) {
logger.error("error message")
}
}
return null
}
Given your answers in the comments, it looks like you're not looking for concurrency here. The best course of action would then be to just make getRequest() a regular function instead of a suspend one.
Assuming you can't change this, you need to call a suspend function from a regular one. To do so, you have several options depending on your use case:
block the current thread while you do your async stuff
make handleRequest a suspend function
make handleRequest take a CoroutineScope to start coroutines with some lifecycle controlled externally, but that means handleRequest will return immediately and the caller has to deal with the running coroutines (please don't use GlobalScope for this, it's a delicate API)
Option 2 and 3 are provided for completeness, but most likely in your context these won't work for you. So you have to block the current thread while handleRequest is running, and you can do that using runBlocking:
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
runBlocking {
// do your stuff
}
return null
}
Now what to do inside runBlocking depends on what you want to achieve.
if you want to process elements sequentially, simply call getResponse directly inside the loop:
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
runBlocking {
event?.records?.forEach {
try {
val response = getResponse(it.body)
// do something with the response
} catch (ex: Exception) {
logger.error("error message")
}
}
}
return null
}
If you want to process elements concurrently, but independently, you can use launch and put both getResponse() and the code using the response inside the launch:
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
runBlocking {
event?.records?.forEach {
launch { // coroutine scope provided by runBlocking
try {
val response = getResponse(it.body)
// do something with the response
} catch (ex: Exception) {
logger.error("error message")
}
}
}
}
return null
}
If you want to get the responses concurrently, but process all responses only when they're all done, you can use map + async:
override fun handleRequest(event: SQSEvent?, context: Context?): Void? {
runBlocking {
val responses = event?.records?.mapNotNull {
async { // coroutine scope provided by runBlocking
try {
getResponse(it.body)
} catch (ex: Exception) {
logger.error("error message")
null // if you want to still handle other responses
// you could also throw an exception otherwise
}
}
}.map { it.await() }
// do something with all responses
}
return null
}
You can use GlobalScope.async() instead of launch() - it returns Deferred, which is a future/promise object. You can then call await() on it to get a result of getResponse().
Just make sure not to do something like: async().await() - it wouldn't make any sense, because it would still run synchronously. If you need to run getResponse() on all event.records in parallel, then you can first go in loop and collect all deffered objects and then await on all of them.
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()
}
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.