Skip to content

Commit d166eb6

Browse files
committed
Use ServiceLoader to find instances of CoroutineExceptionHandler;
Use Thread.getUncaughtExceptionPreHandler on Android to make sure that exceptions are logged before crash. Also update versions for Android example app. Fixes #148
1 parent cdc5865 commit d166eb6

File tree

6 files changed

+48
-10
lines changed

6 files changed

+48
-10
lines changed

core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt

+7-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package kotlinx.coroutines.experimental
1818

19+
import java.util.*
1920
import kotlin.coroutines.experimental.AbstractCoroutineContextElement
2021
import kotlin.coroutines.experimental.CoroutineContext
2122

@@ -29,7 +30,8 @@ import kotlin.coroutines.experimental.CoroutineContext
2930
* (because that is the supposed mechanism to cancel the running coroutine)
3031
* * Otherwise:
3132
* * if there is a [Job] in the context, then [Job.cancel] is invoked;
32-
* * and current thread's [Thread.uncaughtExceptionHandler] is invoked.
33+
* * all instances of [CoroutineExceptionHandler] found via [ServiceLoader] are invoked;
34+
* * current thread's [Thread.uncaughtExceptionHandler] is invoked.
3335
*/
3436
fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
3537
context[CoroutineExceptionHandler]?.let {
@@ -40,6 +42,10 @@ fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
4042
if (exception is CancellationException) return
4143
// try cancel job in the context
4244
context[Job]?.cancel(exception)
45+
// use additional extension handlers
46+
ServiceLoader.load(CoroutineExceptionHandler::class.java).forEach { handler ->
47+
handler.handleException(context, exception)
48+
}
4349
// use thread's handler
4450
val currentThread = Thread.currentThread()
4551
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)

ui/kotlinx-coroutines-android/example-app/app/build.gradle

+6-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ apply plugin: 'kotlin-android-extensions'
44

55
android {
66
compileSdkVersion 25
7-
buildToolsVersion "25.0.2"
7+
buildToolsVersion '26.0.2'
88
defaultConfig {
99
applicationId "com.example.app"
1010
minSdkVersion 9
@@ -22,18 +22,18 @@ android {
2222
}
2323

2424
repositories {
25-
mavenLocal()
26-
mavenCentral()
25+
jcenter()
26+
google()
2727
}
2828

2929
dependencies {
3030
compile fileTree(dir: 'libs', include: ['*.jar'])
3131
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
3232
exclude group: 'com.android.support', module: 'support-annotations'
3333
})
34-
compile 'com.android.support:appcompat-v7:25.2.0'
35-
compile 'com.android.support.constraint:constraint-layout:1.0.1'
36-
compile 'com.android.support:design:25.2.0'
34+
compile 'com.android.support:appcompat-v7:25.4.0'
35+
compile 'com.android.support.constraint:constraint-layout:1.0.2'
36+
compile 'com.android.support:design:25.4.0'
3737
testCompile 'junit:junit:4.12'
3838
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
3939
compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.19.3"

ui/kotlinx-coroutines-android/example-app/build.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ buildscript {
44
ext.kotlin_version = '1.1.51'
55
repositories {
66
jcenter()
7+
google()
78
}
89
dependencies {
9-
classpath 'com.android.tools.build:gradle:2.3.0'
10+
classpath 'com.android.tools.build:gradle:3.0.0'
1011
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1112

1213
// NOTE: Do not place your application dependencies here; they belong
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
#Mon Dec 28 10:00:20 PST 2015
1+
#Sun Nov 05 23:56:59 MSK 2017
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6-
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package kotlinx.coroutines.experimental.android
2+
3+
import kotlinx.coroutines.experimental.CoroutineExceptionHandler
4+
import java.lang.reflect.Modifier
5+
import kotlin.coroutines.experimental.AbstractCoroutineContextElement
6+
import kotlin.coroutines.experimental.CoroutineContext
7+
8+
private val getter =
9+
try {
10+
Thread::class.java.getDeclaredMethod("getUncaughtExceptionPreHandler").takeIf {
11+
Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers)
12+
}
13+
}
14+
catch (e: Throwable) { null /* not found */ }
15+
16+
/**
17+
* Uses Android's `Thread.getUncaughtExceptionPreHandler()` whose default behavior is to log exception.
18+
* See
19+
* [here](https://github.com/aosp-mirror/platform_frameworks_base/blob/2efbc7239f419c931784acf98960ed6abc38c3f2/core/java/com/android/internal/os/RuntimeInit.java#L142)
20+
*
21+
* @suppress This is an internal impl class.
22+
*/
23+
class AndroidExceptionPreHandler :
24+
AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler
25+
{
26+
override fun handleException(context: CoroutineContext, exception: Throwable) {
27+
(getter?.invoke(null) as? Thread.UncaughtExceptionHandler)
28+
?.uncaughtException(Thread.currentThread(), exception)
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
kotlinx.coroutines.experimental.android.AndroidExceptionPreHandler

0 commit comments

Comments
 (0)