为什么协程异常处理程序将原始异常加倍?

why the coroutine exception handler double the original exception?

我实现了自己的 async,我无法以正确的方式处理异常。为什么?

val expected = IllegalStateException();
val it = async<Any> {
    throw expected;
};

assert.that({ it.get() }, throws(equalTo(expected)));
//              ^--- but it throws a IllegalStateException(cause = expected)

源代码

interface Response<in T> {
    suspend fun yield(value: T);
}

interface Request<out T> {
    fun get(): T;
    fun <R> then(mapping: (T) -> R): Request<R>;
}

private val executor: ExecutorService = ForkJoinPool(20);
fun <T> async(block: suspend Response<T>.() -> Unit): Request<T> {
    return object : Request<T>, Response<T> {
        @Volatile var value: T? = null;

        var request: Continuation<Unit>? = block.createCoroutine(this, delegate {}).let {
            var task: Future<*>? = executor.submit { it.resume(Unit); };
            return@let delegate {
                try {
                    val current = task!!;
                    task = null;
                    current.get();
                } catch(e: ExecutionException) {
                    throw e.cause ?: e;
                }
            };
        };

        override fun <R> then(mapping: (T) -> R): Request<R> = async<R> {
            yield(mapping(get()));
        };

        override fun get(): T {
            return value ?: wait();
        }

        private fun wait(): T {
            val it = request!!;
            request = null;
            it.resume(Unit);
            return value!!;
        }

        suspend override fun yield(value: T) {
            this.value = value;
        }

    };
}

inline fun <T> delegate(noinline exceptional: (Throwable) -> Unit = { throw it; }, crossinline resume: (T) -> Unit): Continuation<T> {
    return object : Continuation<T> {
        override val context: CoroutineContext = EmptyCoroutineContext;


        override fun resumeWithException(exception: Throwable) {
            exceptional(exception);
        }

        override fun resume(value: T) {
            resume(value);
        }
    }
}

奇怪的行为来自 java。 ForkJoinTask#getThrowableException 将为给定任务重新抛出异常:

Returns a rethrowable exception for the given task, if available. To provide accurate stack traces, if the exception was not thrown by the current thread, we try to create a new exception of the same type as the one thrown, but with the recorded exception as its cause. If there is no such constructor, we instead try to use a no-arg constructor, followed by initCause, to the same effect. If none of these apply, or any fail due to other exceptions, we return the recorded exception, which is still correct, although it may contain a misleading stack trace.

这意味着如果您不想为给定任务重新抛出异常,您可以将异常构造函数设为非公开,例如:

val exception = object: IllegalStateException(){/**/};
//                      ^--- its constructor only available in its scope