gmariotti
12/25/2018, 9:24 AM@Test
fun `test failure event method`(testContext: TestContext) {
GlobalScope.launch(vertx.dispatcher()) {
val async = testContext.async()
val cause = RuntimeException()
try {
awaitEvent<Any> { throw cause }
testContext.fail()
} catch (e: Exception) {
testContext.assertEquals(cause, e)
}
async.complete()
}
}
with awaitEvent
implemented in this way:
suspend fun <T> awaitEvent(block: (h: Handler<T>) -> Unit): T {
return suspendCancellableCoroutine { cont: CancellableContinuation<T> ->
try {
block.invoke(Handler { t ->
cont.resume(t)
})
} catch (e: Exception) {
cont.resumeWithException(e)
}
}
}
and now this test pass only if I do testContext.assertEquals(cause, e.cause)
louiscad
12/25/2018, 11:13 AMcoroutineScope { … }
wrapping the code that can throw?elizarov
12/25/2018, 11:19 AMe.printStackTrace()
gmariotti
12/26/2018, 9:36 AMelizarov
12/26/2018, 9:45 AMgmariotti
12/26/2018, 10:17 AMpakoito
12/27/2018, 10:37 PMThe type and message of exception are preserved, so that your usual exception-handling logic can still workOur logic was based off referential comparison, which is an approach other concurrency libraries respect. Comparing by type and message string provides weaker assurances about identity, on top of a perf hit. Is this change strictly necessary for the library, or could it be triggered by a configuration flag, or any alternative solution?
elizarov
12/28/2018, 10:20 AMComparing by type and message string provides weaker assurances about identity, on top of a perf hit.This is not true and, unfortunately, cannot be true. Exceptions on JVM are mutable and this prevents their reuse in concurrent setting. We use the similar logic of copying exceptions to that in the JVM’s
CompletableFuture
implementation.pakoito
12/28/2018, 9:47 PMCompletableFuture
doesn’t put me more at ease, there’s a reason why we’re reimplementing it at every turn hahahaha
The goal of having human-readable exceptions of ease of debugging trumps all other considerations.I’m for that goal too, I’m just concerned about the implementation and I don’t think it’s the only possible option. I have several concerns: firstly, wouldn’t it hinder debuggability the fact that debug and production have different stacktraces? Secondly, in other concurrent frameworks like the ones mentioned above they’re using other APIs like
Throwable#addSuppressed
that don’t break referential equality and make this a problem only in the case where there are concurrent exceptions; instead of all of them. We have had the same problem when writing parallelMap and races for IO
in Arrow, and that’s the approach we are currently considering.