Add ProGuard instructions to make sure AndroidExceptionPreHandler
is kept on Android when using minification. Make sure that original
app exception is not "eaten" any case.
Fixes #214
diff --git a/README.md b/README.md
index f43d212..789d0ea 100644
--- a/README.md
+++ b/README.md
@@ -101,8 +101,15 @@
In obfuscated code, fields with different types can have the same names,
and `AtomicReferenceFieldUpdater` may be unable to find the correct ones.
To avoid field overloading by type during obfuscation, add this to your config:
+
```
-keepclassmembernames class kotlinx.** {
volatile <fields>;
}
```
+
+You also need to keep this class if you build your Android releases with `minifyEnabled true`:
+
+```
+-keep class kotlinx.coroutines.experimental.android.AndroidExceptionPreHandler
+```
diff --git a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt
index a8ef8bd..2bb0cc7 100644
--- a/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt
+++ b/core/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/CoroutineExceptionHandler.kt
@@ -33,21 +33,31 @@
* * current thread's [Thread.uncaughtExceptionHandler] is invoked.
*/
public actual fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
- context[CoroutineExceptionHandler]?.let {
- it.handleException(context, exception)
- return
+ // if exception handling fails, make sure the original exception is not lost
+ try {
+ context[CoroutineExceptionHandler]?.let {
+ it.handleException(context, exception)
+ return
+ }
+ // ignore CancellationException (they are normal means to terminate a coroutine)
+ if (exception is CancellationException) return
+ // try cancel job in the context
+ context[Job]?.cancel(exception)
+ // use additional extension handlers
+ ServiceLoader.load(CoroutineExceptionHandler::class.java).forEach { handler ->
+ handler.handleException(context, exception)
+ }
+ // use thread's handler
+ val currentThread = Thread.currentThread()
+ currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
+ } catch (handlerException: Throwable) {
+ // simply rethrow if handler threw the original exception
+ if (handlerException === exception) throw exception
+ // handler itself crashed for some other reason -- that is bad -- keep both
+ throw RuntimeException("Exception while trying to handle coroutine exception", exception).apply {
+ addSuppressed(handlerException)
+ }
}
- // ignore CancellationException (they are normal means to terminate a coroutine)
- if (exception is CancellationException) return
- // try cancel job in the context
- context[Job]?.cancel(exception)
- // use additional extension handlers
- ServiceLoader.load(CoroutineExceptionHandler::class.java).forEach { handler ->
- handler.handleException(context, exception)
- }
- // use thread's handler
- val currentThread = Thread.currentThread()
- currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
/**