I got this exception on Android while playing around with options for handling errors.
It doesn't like me giving a CoroutineExceptionHandler into a GlobalScope.launch.
class MainActivity : AppCompatActivity(), CoroutineScope {
private lateinit var job: Job
private val coroutineExceptionHandler = CoroutineExceptionHandler { _, t ->
AlertDialog.Builder(this)
.setTitle("Exception")
.setMessage(t.message)
.setNeutralButton("Ok") { d, _ -> d.dismiss() }
.show()
}
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main + coroutineExceptionHandler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
setContentView(R.layout.activity_main)
launch {
GlobalScope.launch(coroutineExceptionHandler) { throw RuntimeException("asdfadsf") }.join()
test()
}
}
suspend fun test() {
throw RuntimeException("YTay")
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
}
AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
Process: com.wmba.food.android, PID: 13835
java.lang.RuntimeException: Exception while trying to handle coroutine exception
at kotlinx.coroutines.CoroutineExceptionHandlerKt.handlerException(CoroutineExceptionHandler.kt:56)
at kotlinx.coroutines.CoroutineExceptionHandlerKt.handleExceptionViaHandler(CoroutineExceptionHandler.kt:46)
at kotlinx.coroutines.StandaloneCoroutine.handleJobException(Builders.common.kt:159)
at kotlinx.coroutines.JobSupport.tryFinalizeFinishingState(JobSupport.kt:220)
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:792)
at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:735)
at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:117)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:45)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:236)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
Caused by: java.lang.RuntimeException: Can't create handler inside thread Thread[DefaultDispatcher-worker-1,5,main] that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:205)
at android.os.Handler.<init>(Handler.java:118)
at android.app.Dialog.<init>(Dialog.java:123)
at android.app.Dialog.<init>(Dialog.java:168)
at android.support.v7.app.AppCompatDialog.<init>(AppCompatDialog.java:57)
at android.support.v7.app.AlertDialog.<init>(AlertDialog.java:98)
at android.support.v7.app.AlertDialog$Builder.create(AlertDialog.java:981)
at android.support.v7.app.AlertDialog$Builder.show(AlertDialog.java:1006)
at com.wmba.food.android.MainActivity$$special$$inlined$CoroutineExceptionHandler$1.handleException(CoroutineExceptionHandler.kt:104)
at kotlinx.coroutines.CoroutineExceptionHandlerKt.handleExceptionViaHandler(CoroutineExceptionHandler.kt:42)
at kotlinx.coroutines.StandaloneCoroutine.handleJobException(Builders.common.kt:159)聽
at kotlinx.coroutines.JobSupport.tryFinalizeFinishingState(JobSupport.kt:220)聽
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:792)聽
at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:735)聽
at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:117)聽
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:45)聽
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:236)聽
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)聽
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)聽
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)聽
CoroutineExceptionHandler is called from a random thread. To show an alert from there do:
GlobalScope.launch(Dispatchers.Main) { ... /* put code here */ ... }
If the coroutine is attached to a Dispatchers.Main scope, is it guaranteed to run from the main thread, or is that still a random thread?
No guarantees. This is just a synchonous callback from a random place. We might consider adding some more high-level user-friendly API that launches a new coroutine to handle an error and thus ensures execution context.
^^ That would be nice. The CoroutineExceptionHandler seems like the natural place to provide some default exception handling. When handling the exceptions, it seems very common that we'd want want to update/modify state in a certain thread (especially with kotlin/native), or display some sort of error in the UI. This seems like it would naturally work by calling the coroutine handler from the dispatcher that the coroutine context had been combined with.
Is there a ticket for this that I could follow?
Let it be this one?
馃憤
If the coroutine is attached to a Dispatchers.Main scope, is it guaranteed to run from the main thread, or is that still a random thread?
I am not sure we can provide such guarantee from the implementation perspective that works 100% of the time because of the concurrent nature of coroutines hierarchies.
As a project-specific solution, something like
fun ConfinedExceptionHandler(dispatcher, handler): CoroutineExceptionHandler = CoroutineExceptionHandler { ctx, t ->
GlobalScope.launch(dispatcher) {
try {
handler(ctx, t)
} catch (t: Throwable) {
// handle fatal
}
}
}
can be used.
I don't like it as a library primitive because it postpones actual exception handling and it may lead to very bizarre consequences.
E.g.
val rootJob = launch(ConfinedExceptionHandler(veryBusyDispatcher)) {
...
}
rootJob.join()
// Assuming we are done when rootJoin is complete
System.exit(-1)
In that case, failure may never be logged and that is the real problem.
Another solution is to do something like "invoke handler if we are in the right dispatcher or launch in runBlocking otherwise", but it may lead to deadlocks if a user overrides isDispatchNeeded.
Is there any update to this enhancement?
Most helpful comment
^^ That would be nice. The
CoroutineExceptionHandlerseems like the natural place to provide some default exception handling. When handling the exceptions, it seems very common that we'd want want to update/modify state in a certain thread (especially with kotlin/native), or display some sort of error in the UI. This seems like it would naturally work by calling the coroutine handler from the dispatcher that the coroutine context had been combined with.