When, for example, a NullPointerException occurs after calling the RxJava map operator, the app doesn't crash. I want the app to crash when this happens so it can submit reports to crashlytics etc.
I tried using Exceptions.propagate() in a try/catch block but that didn't work.
The only solution that I found was throwing a RuntimeException in my error handler.
override fun getCategories(): Single<List<Category>> {
return ApiManager
.getCategoriesService()
.getCategories()
.map { categoriesResponse ->
throw KotlinNullPointerException
categoriesResponse.categories?.map { categoryResponse ->
CategoryMapper().mapFromRemote(categoryResponse)
}
}
}
The NullPointerException thrown inside the map operator does not crash the app.
If I call it before the return statement it crashes the app.
If I call it before the return statement it crashes the app.
It crashes because getCategories method is running on Android's main thread to build the rx chain.
The NullPointerException thrown inside the map operator does not crash the app.
It doesn't crash the app because this chain is being subscribed to a thread that is not Android's main thread. E.g. your chain has .subscribeOn(Schedulers.io()).
The only solution that I found was throwing a RuntimeException in my error handler.
That's the expected design of your chain as per this document:
An Observable typically does not throw exceptions. Instead it notifies any observers that an unrecoverable error has occurred by terminating the Observable sequence with an onError notification.
So rather than catch exceptions, your observer or operator should more typically respond to onError notifications of exceptions.
Ok so I think the closest to what I want to achieve is by calling Exceptions.propagate(throwable) in the error handler called in onError.
Thanks for pointing me in the right direction #Gustavo #TooManyEduardos2
Related
val parentScope = CoroutineScope(Dispatchers.Main)
parentScope.launch {
try{
launch{ // child scope
//code ....
throw CustomError("error", null)
}
} catch(cause: CustomError){
// It did not get executed
}
}
In the above code snippet the app got crash. The exception thrown from it is not caught into the parentScope catch block.
But if we replace the above childScope with
supervisorScope or
coroutineScope or
withContext or
runBlocking
it catught the exception.
parentScope.launch {
try{
supervisorScope {
//code
throw CustomError("error", null)
}
} catch(cause: CustomError){
// It get execute when withContext/supervisorScope
}
}
Why exception thrown from child scope(launch) is not caught by parent catch block?
Exception:
FATAL EXCEPTION: DefaultDispatcher-worker-1
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Disclaimer: this question has been edited which greatly affected the answer. Original answer is below.
launch() is used to run the code asynchronously, in a separate coroutine or in the background. CustomError is not really thrown from within try...catch. Code which invokes launch() doesn't even wait for it to start executing - it only schedules the code to be executed and then immediately progress further.
This is similar to starting a new thread. If the thread throws, you can't catch this in the code that created or started the thread. Code that started the thread and the code inside the thread are running in two separate execution contexts.
All other functions you mentioned (coroutineScope(), runBlocking(), etc.) run synchronously - they wait for the block of code to be executed, they wait for its result. Inner block of code runs inside the same execution context as the outer one. For this reason exceptions could be propagated as usual.
Original answer:
In your first example there is really no parent-child relationship. Both parentScope and childScope are created separately and they run independently of each other. Simply because you put one code block inside another doesn't mean they are in any way related.
withContext() is different, it uses the current scope (parentScope) to execute the code.
In other words: whenever you use someScope.launch() this is like explicitly asking to switch to entirely different execution context.
While reading https://developer.android.com/kotlin/coroutines I stumbled upon the following warning:
Warning: launch and async handle exceptions differently. Since async expects an eventual call to await at some point, it holds exceptions and rethrows them as part of the await call. This means if you use await to start a new coroutine from a regular function, you might silently drop an exception. These dropped exceptions won't appear in your crash metrics or be noted in logcat.
However I'm not able to find any example of this silent-dropping behavior while browsing https://kotlinlang.org/docs/reference/coroutines/exception-handling.html or any other resources returned by https://www.google.com/search?q=kotlin+await+exception+handling - on the contrary, all resources indicate that exception thrown in an async/await block will cause a failure in the whole coroutine scope which is correct and expected.
I'm afraid I'm missing something here, can you provide an example where this silent exception dropping occurs which cannot be noted in logcat?
The passage you quote from Kotlin documentation is outdated. It used to be true in the experimental phase, and you really could get swallow exceptions if you weren't very pedantic.
The release version of coroutines acquired an additional key component: structured concurrency. When following the simple guidelines not to use GlobalScope and similar, your code will automatically be organized such that each coroutine has a parent and there's a well-defined scope within which all coroutines must complete either normally or abruptly, and the owner of that scope can await its completion, getting the exception that caused the abnormal completion.
Within this new discipline, launch and async are on the same footing. If an exception thrown inside one of them escapes its top-level block, it goes on to cancel the coroutine, notify the parent of this, and then the parent may opt to cancel all the other children so that the whole scope ends prematurely.
Note that getting an exception from the await call is an entirely different mechanism. The thing you await on is not the async block itself, but a standalone object of type Deferred, which is identical to Java's Future except that await suspends instead of blocks. The async block, upon completion, always does the same: it completes the Deferred with either the return value or the exception. You may retrieve that result from anywhere you pass the Deferred, it's completely decoupled from the destiny of the async coroutine.
Ok, I found an example which is in line with Marko's answer concerning structured concurrency. Code snippet below presents the silent exception dropping usecase.
This example comes from https://medium.com/androiddevelopers/coroutines-on-android-part-ii-getting-started-3bff117176dd
val unrelatedScope = MainScope()
// example of a lost error
suspend fun lostError() {
// async without structured concurrency
unrelatedScope.async {
throw InAsyncNoOneCanHearYou("except")
}
}
Note this code is declaring an unrelated coroutine scope that will launch a new coroutine without structured concurrency. [...]
The error is lost in this code because async assumes that you will eventually call await where it will rethrow the exception. However, if you never do call await, the exception will be stored forever waiting patiently to be raised.
Exceptions from launch are thrown "instantly" into exception handler, while those from async will be thrown the moment you call await.
Sample code:
import kotlinx.coroutines.*
fun main(args: Array<String>) = runBlocking{
println("main start")
val l = GlobalScope.launch{ justThrow("launch") }
val a = GlobalScope.async { justThrow("async") }
delay(500)
// a.await()
println("main finished")
}
suspend fun justThrow(who : String){
println("starting $who")
delay(100)
throw Exception("Test exception from $who")
}
Output:
main start
starting launch
starting async
Exception in thread "DefaultDispatcher-worker-2 #coroutine#2" java.lang.Exception: Test exception from launch
at FileKt.justThrow(File.kt:15)
(... entire stack here )
main finished
You can see launch throwing exception in worker thread but there's nothing from async, even though it executed the same function that must fail.
For next run modify the code by removing the launch for clarity and uncommenting a.await():
fun main(args: Array<String>) = runBlocking{
println("main start")
val a = GlobalScope.async { justThrow("async") }
delay(500)
a.await()
println("main finished")
}
Output:
main start
starting async
Exception in thread "main" java.lang.Exception: Test exception from async
at FileKt.justThrow (File.kt:15)
(... entire stack here )
Exception is held instead of crashing in worker thread and is instead re-thrown the moment await() is called which causes the app to crash. You can notice it happened on main thread and there was no "main finished" print because of that.
After reading Kotlin documentation, I came up with the following code (which is not working - see below) to repeat a function call until it returns true, or timeout is reached.
I want to pause execution until this code block reaches timeout or success - it is not supposed to execute asynchronously.
Log.d(TAG, "This is the last line to be logged")
runBlocking {
Log.d(TAG, "this line is never logged")
try {
withTimeout(timeoutMsL) {
while ((isActive) && (!success)) {
success = doSomething()
}
}
}
catch (ex: TimeoutCancellationException) {
Log.d(TAG, "this line is never logged either")
doSomethingElse()
}
}
timeoutMsL is a Long with typical value 50 ms.
This code is called from C++ over the JNI. When I run it
nothing inside the runBlocking block runs
nothing after the runBlocking block runs
control returns to the C++ caller
there is an exception in the JNI, but the JNI doesn't log Kotlin or Java exception details.
no exception is logged in adb
when I tried surrounding the above code snippet with a try/catch/log block to catch Kotlin exceptions, nothing is logged
I have read that runBlocking should be avoided, but also you have to call withTimeout from an existing coroutine.
If I use a normal coroutine, execution of the calling function will continue before timeout /success is reached - I need to prevent this from happening.
How should this be coded in Kotlin?
Your problem probably lies in doSomething(). Kotlin's coroutine implementation relies a lot on cooperative execution where child coroutines check flags to see if they have been cancelled (as withTimeout() would do). This would mean the outer coroutines will pause until they confirm the child coroutines have ended, blocking the entire function.
if doSomething never suspends and never checks if it is still active it will just run until completion regardless of the external situation.
To fix this, there are two options:
Make doSomething() a suspend function and regularly suspend with either yield() or ensureActive() to respond to cancellation.
Execute it on a dispatcher that is designed to interrupt normal blocking code like withContext(Dispatchers.IO).
I have a simple stream like that:
Observable.error<Int>(Exception()).startWith(1).subscribe {
println("Item is $it")
}
Everything is working like expected. First onNext is called with integer 1 and then exception is thrown, however when I change the stream by adding observeOn like that:
Observable.error<Int>(Exception()).startWith(1).observeOn(AndroidSchedulers.mainThread()).subscribe {
println("Item is $it")
}
onNext is never called. Only the exception is thrown. What am I missing here?
From the observeOn document
Note that onError notifications will cut ahead of onNext notifications
on the emission thread if Scheduler is truly asynchronous.
That means when you apply it, the onError is emitted first & hence the onNext is not called as the streams has ended due to onError.
You can do the following in order to receive the onNext first
observeOn(AndroidSchedulers.mainThread(), true)
This tells the Observable to delay the error till the onNext of startWith is passed
Currently I am investigating a migration to RxJava and decided that a manager of mine(accountManager) would be an interesting place to start. Currently the Manager has a list of listeners and sends updates accordingly, both when the account gets updated and when something goes wrong.
private List<WeakReference<ProfileChangeListener>> mListeners = new ArrayList<>();
public interface ProfileChangeListener {
void onProfileUpdated(Account account);
void onProfileFailed(Exception e);
}
My Rx solution involves a Subject
private SerializedSubject<Account, Account> mManagerSubject = new SerializedSubject<>(BehaviorSubject.<Account>create());
public Observable<Account> observe() {
return mManagerSubject;
}
and then when an update happens I call one of the following:
private void onProfileUpdated(Account account) {
mManagerSubject.onNext(account);
}
private void onProfileFailed(final Exception e) {
mManagerSubject.onError(e);
}
Issue
The Issue is that once onError is called anyone listening via observe will never get another update from onNext.
I still want the subscribers to receive onError so they can handle the error state but at a later time onNext could still be called with an updated account and I still want the subscribers to handle the updated account.
I've tried solutions using onErrorResumeNext, onErrorReturn onExceptionResumeNext but none of them propagate the onError.
TLDR: How do I keep the subscribers subscribed after onError is called while still propagating onError?
"Errors" in Rx can be a a little difficult to grasp at first, because they have a slightly different meaning from what most people expect.
From the Error Handling documentation (emphasis mine):
An Observable typically does not throw exceptions. Instead it notifies any observers that an unrecoverable error has occurred by terminating the Observable sequence with an onError notification.
onError() is supposed to be used when an Observable encounters an unrecoverable error- that is when your Observable cannot continue emitting items. When you are subscribing, you might use something like onErrorResumeNext to try some recovery action, but that should be the end of the source Observable.
Instead, you may want to adjust what your Observable emits to support emitting an error item, or include a flag indicating that an error was encountered.
If your error truly is unrecoverable, then you may want to revisit your recovery strategy and try a slightly different approach.