Stop library from swallowing all exceptions in Kotlin - 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()
}

Related

Not able to read annotation property inside Aspect when writing pointcut in a method

I have a custom annotation as
annotation class MyHandler(val value:String)
I wrote an aspect for functions using this annotation, also reading the value within the annotation for my processing.
#Around("#annotation(myHandler)")
fun handleExceptions(joinPoint: ProceedingJoinPoint, myHandler: MyHandler) {
println(myHandler.value) //this works
try {
joinPoint.proceed()
} catch (e: Exception){
}
}
This works perfectly fine.
I then tried to move the pointcut definition to a separate function as below and this is where I am running into issues.
#Pointcut("#annotation(com.example.demo.MyHandler)")
fun myHandlerFunc() {}
#Around(value = "myHandlerFunc()")
fun handleExceptions(joinPoint: ProceedingJoinPoint, myHandler: MyHandler) {
println(myHandler.value) //this works
try {
joinPoint.proceed()
} catch (e: Exception){
}
}
Error being -
Caused by: java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut.
Any way I could still have access to the annotation properties, while having the pointcut definition in a separate method?
Got this to work after some trial and error
#Pointcut("#annotation(myHandler)")
fun myHandlerFunc(myHandler: MyHandler) {}
#Around(value = "myHandlerFunc(myHandler)")
fun handleExceptions(joinPoint: ProceedingJoinPoint, myHandler: MyHandler) {
println(myHandler.value)
try {
joinPoint.proceed()
} catch (e: Exception){
}
}
Is there a better or cleaner way to accomplish this?

kotlin coroutineScope exception cannot be handled

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

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()

Gather multiple async results in Kotlin Coroutines ignoring the exceptions with timeouts

I have a Generator class that, basically, generates some data, like:
interface Generator {
suspend fun generate(): String?
}
There are multiple implementations. Some of them may throw exceptions and some of them may took too long to generate the data:
class Faulty : Generator {
override suspend fun generate(): String? {
println("Faulty")
throw IllegalArgumentException();
}
}
class Lingering : Generator {
override suspend fun generate(): String? {
println("Lingering")
delay(Duration.ofHours(1))
return null
}
}
But some implementations are worthy
class Good : Generator {
override suspend fun generate(): String {
println("Good")
return "Goooood"
}
}
What I need to do is to gather the data generated by a list of pre-configured generators, giving each of them a timeout for its generate and ignoring the exceptions (but logging them):
fun main() = runBlocking {
val generators = listOf(Faulty(), Lingering(), Good())
val results = supervisorScope {
generators
.map { generator ->
async(CoroutineExceptionHandler { context, exception ->
println(exception)
}) {
withTimeoutOrNull(5000) {
generator.generate()
}
}
}
.awaitAll()
.filterNotNull()
}
println(results)
}
The problem is that this code fails with exception:
Faulty
Lingering
Good
Exception in thread "main" java.lang.IllegalArgumentException
at Faulty.generate (File.kt:12)
at FileKt$main$1$results$1$1$2$1.invokeSuspend (File.kt:41)
at FileKt$main$1$results$1$1$2$1.invoke (File.kt:-1)
Why doesn't the supervisorScope catch it? What am I doing wrong?
From the documentation of CoroutineExceptionHandler:
An optional element in the coroutine context to handle uncaught exceptions.
and
A coroutine that was created using async always catches all its exceptions and represents them in the resulting Deferred object, so it cannot result in uncaught exceptions.
so it follows that your async job doesn't emit an uncaught exception. The exception is rethrown by the awaitAll() call that happens later. You have put your uncaught exception handler only within your async context, so it will not be used.
Furthermore, children coroutines do not emit uncaught exceptions anyway. Their exceptions are delegated up to their root ancestor.
As explained here in the last section titled Exceptions in supervised coroutines, children of a supervisor scope must have a root coroutine that uses the handler.
What you can do is wrap the whole task in a launch block that uses the handler. For some reason it doesn't work to install the handler on runBlocking. Maybe that doesn't count as a root job?
fun main() = runBlocking{
val job = GlobalScope.launch(CoroutineExceptionHandler { context, exception ->
println(exception)
}) {
val generators = listOf(Faulty(), Lingering(), Good())
val results =
supervisorScope {
generators
.map { generator ->
async {
withTimeoutOrNull(5000) {
generator.generate()
}
}
}
.awaitAll()
.filterNotNull()
}
println(results)
}
job.join()
}
But I think maybe the only reason you introduced the CoroutineExceptionHandler was for ignoring exceptions. That strategy won't work, because the handler only deals with uncaught exceptions, meaning it's too late to recover. The job has already failed at that point. You will have to wrap your generate() call within the async block in a try/catch or runCatching.

Kotlin functional way

I'm trying to perfect myself in Kotlin with functional programming. And then I did this:
I was tired of the way I write try - catch, and created the following function:
package com.learning.functionalway
fun <T> tryCatch(t: T?, excpetion: (Throwable)): T? = try {
t
} catch (e: Exception) {
throw excpetion
}
And I used it like this:
#Service
class ProductService(val repository: IProductRepository, val repositoryS: IStockRepository) : IService<Product, ProductModel> {
override fun find(id: Long) = tryCatch(
repository.find(id),
DataNotFound("Product not found"))
other methods ..
}
And my exception that I deal in the "Exception Handler"
class DataNotFound(message: String?) : Exception(message) {
}
Is this a correct way I used to modify the way I use try - catch?
Or are there better ways to do it?
Your solution is not a "more functional" way of doing error handling but rather just arguably a slight improvement in try-catch syntax.
If you truly want to embrace functional programming, I'd recommend you to check out Arrow. The standard Kotlin library is not enough for advanced functional programming concepts (such as error handling) and Arrow fills that gap.
You can read their documentation on how to do proper error handling.
If you fancy a talk about it, I'd recommend you to check out this video (topic of error handling starts here) which is about Kotlin and functional programming.
One way to remake the try-catch syntax to make it more functional is like this:
sealed class Try<out Output> {
class Some<Output>(val output: Output) : Try<Output>()
class None(val exception: Exception) : Try<Nothing>()
companion object {
operator fun <Output> invoke(toTry: () -> Output) = try {
Some(toTry())
} catch (e: Exception) {
None(e)
}
}
val value get() = when(this) {
is Some -> output
is None -> null
}
infix fun catch(onException: (Exception) -> Unit): Output? = when (this) {
is Some -> output
is None -> {
onException(exception)
null
}
}
}
class ProductService(val repository: IProductRepository, val repositoryS: IStockRepository) : IService<Product, ProductModel> {
override fun find(id: Long): Product? = Try {
repository.find(id)
} catch { exception ->
println("Error trying to get product $exception")
}
//other methods ..
}
The key advantage here is that unlike in the original syntax you can do things by parts. So if you have a lot of tries to do and want to handle all the results at the end, with this syntax you can.