From 5d57c33789e3142f221cf3b7a948d396d82bc4d6 Mon Sep 17 00:00:00 2001 From: Him188 Date: Tue, 29 Jun 2021 22:49:32 +0800 Subject: [PATCH] Improve `Throwable.unwrap`: add the unwrapped-out exception to suppressed exceptions to avoid loss of debugging information --- .../src/commonMain/kotlin/CoroutineUtils.kt | 58 +++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt b/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt index 2d3c146f7..2db1e6b6c 100644 --- a/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt +++ b/mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt @@ -104,15 +104,65 @@ public inline fun runUnwrapCancellationException(block: () -> R): R { } catch (e: CancellationException) { // e is like `Exception in thread "main" kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=JobImpl{Cancelled}@f252f300` // and this is useless. - if (e.suppressedExceptions.isNotEmpty()) throw e // preserve details. - throw e.findCause { it !is CancellationException } ?: e + throw e.unwrapCancellationException() + + // if (e.suppressed.isNotEmpty()) throw e // preserve details. + // throw e.findCause { it !is CancellationException } ?: e } } public fun Throwable.unwrapCancellationException(): Throwable = unwrap() +/** + * For code + * ``` + * try { + * job(new) + * } catch (e: Throwable) { + * throw IllegalStateException("Exception in attached Job '$name'", e.unwrapCancellationException()) + * } + * ``` + * + * Original stacktrace, you mainly see `StateSwitchingException` which is useless to locate the code where real cause `ForceOfflineException` is thrown. + * ``` + * Exception in thread "DefaultDispatcher-worker-1 @BotInitProcessor.init#7" java.lang.IllegalStateException: Exception in attached Job 'BotInitProcessor.init' + * at net.mamoe.mirai.internal.network.handler.state.JobAttachStateObserver$stateChanged0$1.invokeSuspend(JobAttachStateObserver.kt:40) + * at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) + * at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104) + * 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) + * Caused by: StateSwitchingException(old=StateLoading, new=StateClosed, cause=net.mamoe.mirai.internal.network.impl.netty.ForceOfflineException: Closed by MessageSvc.PushForceOffline: net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushForceOffline@4abf6d30) + * at net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport.setStateImpl$mirai_core(NetworkHandlerSupport.kt:258) + * at net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler.close(NettyNetworkHandler.kt:404) + * ``` + * + * Real stacktrace (with [unwrapCancellationException]), you directly have `ForceOfflineException`, also you wont lose information of `StateSwitchingException` + * ``` + * Exception in thread "DefaultDispatcher-worker-2 @BotInitProcessor.init#7" java.lang.IllegalStateException: Exception in attached Job 'BotInitProcessor.init' + * at net.mamoe.mirai.internal.network.handler.state.JobAttachStateObserver$stateChanged0$1.invokeSuspend(JobAttachStateObserver.kt:40) + * at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) + * at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104) + * 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) + * Caused by: net.mamoe.mirai.internal.network.impl.netty.ForceOfflineException: Closed by MessageSvc.PushForceOffline: net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushForceOffline@62f65f94 + * at net.mamoe.mirai.utils.MiraiUtils__CoroutineUtilsKt.unwrapCancellationException(CoroutineUtils.kt:141) + * at net.mamoe.mirai.utils.MiraiUtils.unwrapCancellationException(Unknown Source) + * ... 7 more + * Suppressed: StateSwitchingException(old=StateLoading, new=StateClosed, cause=net.mamoe.mirai.internal.network.impl.netty.ForceOfflineException: Closed by MessageSvc.PushForceOffline: net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushForceOffline@62f65f94) + * at net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport.setStateImpl$mirai_core(NetworkHandlerSupport.kt:258) + * at net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler.close(NettyNetworkHandler.kt:404) + * ``` + */ + public inline fun Throwable.unwrap(): Throwable { if (this !is E) return this - if (suppressedExceptions.isNotEmpty()) return this - return this.findCause { it !is E } ?: this + if (suppressed.isNotEmpty()) return this + return this.findCause { it !is E } + ?.also { it.addSuppressed(this) } + ?.fillInStackTrace() // add the unwrapped CancellationException to suppress so we loss no information + ?: this } \ No newline at end of file