Android kotlin coroutine: await might silently drop exceptions - android

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.

Related

Why exception thrown from child scope (launch) is not caught by parent catch block in Kotlin coroutine?

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.

every Espresso "perform" gives same error: "cannot be called on the main application thread"

Writing the first instrumentation tests for an existing application's activities. The first test I've written errors out saying about onView(...).perform(typeText(...), closeSoftKeyboard())
Method cannot be called on the main application thread
here's the test so far:
class LoginActivityTests {
#get:Rule var rule = ActivityScenarioRule(LoginActivity::class.java)
#Test
fun whenClickLoginStoresDomainInConnectionManager() {
val domainName = "foo"
val scenario = rule.scenario
scenario.onActivity {
onView(withId(R.id.domainEditText)).perform(typeText(domainName), closeSoftKeyboard())
}
}
}
The error is thrown on the onView with abbreviated stacktrace
java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main)
at androidx.test.internal.util.Checks.checkState(Checks.java:111)
at androidx.test.internal.util.Checks$1.checkNotMainThread(Checks.java:150)
at androidx.test.internal.util.Checks.checkNotMainThread(Checks.java:130)
at androidx.test.espresso.ViewInteraction.postAsynchronouslyOnUiThread(ViewInteraction.java:1)
at androidx.test.espresso.ViewInteraction.desugaredPerform(ViewInteraction.java:4)
at androidx.test.espresso.ViewInteraction.perform(ViewInteraction.java:8)
I've looked at sample tests for performing clicks and text typing, and none of them seem to use any explicit specification of UI threads or anything. They just do what I've done: annotate a non-suspend function as #Test and then invoke onView(...).perform(...)
I even tried to explicitly run in UI thread:
it.runOnUiThread {
onView(withId(R.id.domainEditText)).perform(typeText(domainName), closeSoftKeyboard())
onView(withId(R.id.loginButton)).perform(click())
Log.d(TAG, "performed click")
}
Same error, same line. I've also installed android.test:rules and put #UiThreadTest on my #Before (even though there's not any UI stuff there, apparently the annotation can only take effect on After and Before methods.
Edit Question is: how do I get perform actions to work in my tests? Why am I getting this error when all the sample tests I'm finding with perform actions never have to specify UI thread or anything?

How to fix Kotlin JobCancellationException?

I got a crash because of Kotlin JobCancellationException.
The following is the detail about the crash :
kotlinx.coroutines.JobCancellationException: Job was cancelled; job=SupervisorJobImpl{Cancelling}#131dbe3
All I know is the SupervisorJobImpl is for ViewModelScope, and it will be called method cancel when ViewModel lifecycle is over.
I was so confused about the Exception because Kotlin coroutines will just ignore the Exception, but it was thrown and cause the App crash. If it has stack I can just figure out, but it doesn't, just tell me that the job was cancelled.
I spent about more than 3 days on the exception but just have no idea.
I saw the video :
KotlinConf 2019: Coroutines! Gotta catch 'em all! by Florina Muntenescu & Manuel Vivo, I found if the scope is canceled, and if you call await on a Deferred, it will throw the Exception, but I found no await on the canceled scope.
So can someone just show me some code which perhaps causes the same exception and make the App crash? Thx, there.
Finally, I found what causes the Exception and the issue address is flowing:
kotlin.coroutines.channels.awaitClose: JobCancellationException
Actually, awaitClose will not throw the JobCancellationException, because awaitClose is a cancellable suspended function. The offer method will throw JobCancellationException if the Job was canceled because offer is not a cancellable suspended function.
By the way, callbackFlow is an experimental API, so it may cause some bug, so when we use it, we need to be careful. Because it will not always ignore JobCancellationException when Job was canceled, and I don't think it's friendly to developers.
Now I have found 2 situations that will cause JobCancellationException so we need to try catch the exception.
async await, when we call the await method we need to try catch. And you can find and example in the Video.
callbackFlow offer, when we call the offer method we need to try catch. And you can find an example in the issue above.
I know I am late but you can just check Job Status before offering objects.
Like this
if(isActive) offer(Resource.success(response))
isActive is Coroutine Scope
I just saw the same problem. The issue was caused by the activity being finished manually before the job managed to complete.
I closed a ViewModel and a dialog and then started a Job. It led to this exception and HTTP-request cancellation: HTTP FAILED: java.io.IOException: Canceled.
close()
modelScope.launch {
val response = withContext(Dispatchers.IO) {
...
}
response?.let { ... }
}
I simply moved close() to the end.
When you call one coroutine from another, parent coroutine will cancel nested, if finish quicker. For instance:
viewModelScope.launch {
// Launch a request.
...
// Now launch a nested coroutine.
viewModelScope.launch { // (1)
... // Can be cancelled. (2)
}
}
You can remove viewModelScope.launch { in (1) and rewrite a nested block as suspended. If you want a parallel work, use async/await or this:
viewModelScope.launch {
...
}
// A parallel coroutine.
viewModelScope.launch {
...
}
There is a case when everything works well, but once in 100 clicks a coroutine cancels itself (the same "HTTP FAILED: java.io.IOException: Canceled"). Nothing helped, until I changed lifecycleScope.launch to lifecycleScope.launchWhenResumed.
class MyApp: Application() {
override fun onCreate() {
super.onCreate()
System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON)
}
in this way you will be able to pinpoint the main cause of the issue an deal with it
I have the same bug as you. So I try to write simple flow extensions like below:
fun <P> Flow<DataResult<P>>.safeStart(start: suspend FlowCollector<DataResult<P>>.() -> Unit)
: Flow<DataResult<P>> = onStart {
if (!currentCoroutineContext().isActive) return#onStart
start()
}
fun <P> Flow<DataResult<P>>.safeCatch(onCatch: suspend FlowCollector<DataResult<P>>.(cause: Throwable) -> Unit)
: Flow<DataResult<P>> = catch {
if (!currentCoroutineContext().isActive) return#catch
onCatch(it)
}
suspend inline fun <P> Flow<DataResult<P>>.safeCollect(crossinline onCollect: suspend (value: DataResult<P>) -> Unit)
: Unit = collect {
if (!currentCoroutineContext().isActive) return#collect
onCollect(it)
}
Had the same crash when I used callbackFlow with offer with coroutines version 1.3.5.
Now instead of offer I use trySend and it's fixed.
Note:
trySend method is available (and offer is deprecated) when you update coroutines version to:
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0

How to write Kotlin-colloquial code to retry synchronously until timeout or success?

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

Kotlin Android Coroutines - suspend function doesn't seem to run in the background

I feel like i'm missing some crucial part to my understanding to how this code below is working:
private fun retrieveAndStore() {
launch(UI) {
val service = retrofit.create(AWSService::class.java)
val response = service.retrieveData().await()
store(data = response)
}
}
private suspend fun store(data: JsonData) {
val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app-db").build()
db.appDao().insert(storyData)
}
This is the error i get when run:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
I don't understand why the network code via retrofit works but the store function fails. I'm hoping someone can tell me what's going on?
Interestingly if i wrap db call with async {}.await it works, does that mean coroutines can only call other coroutines?
Coroutines aren't about running in either foreground or background. They are about the ability to get suspended, just like a native thread gets suspended by the OS, but on the level where you are in control of that behavior.
When you say launch(UI) { some code }, you tell Kotlin to submit "some code" as a task to the GUI event loop. It will run on the GUI thread until explicitly suspended; the only difference is that it won't run right away so the next line of code below the launch(UI) block will run before it.
The magic part comes around when your "some code" encounters a suspendCoroutine call: this is where its execution stops and you get a continuation object inside the block you pass to suspendCoroutine. You can do with that object whatever you want, typically store it somewhere and then resume it later on.
Often you don't see the suspendCoroutine call because it's inside the implementation of some suspend fun you're calling, but you can freely implement your own.
One such library function is withContext and it's the one you need to solve your problem. It creates another coroutine with the block you pass it, submits that coroutine to some other context you specify (a useful example is CommonPool) and then suspends the current coroutine until that other one completes. This is exactly what you need to turn a blocking call into a suspendable function.
In your case, it would look like this:
private suspend fun store(data: JsonData) = withContext(CommonPool) {
val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "app-db").build()
db.appDao().insert(storyData)
}
I'll also add that you're better off creating your own threadpool instead of relying on the system-wide CommonPool. Refer to this thread for details.

Categories

Resources