-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
Version
4.3.2
Context
When attaching an onSuccess()
or onFailure()
handlers to Future
, if the handler throws an (unchecked) exception - the exception is not propagated to the next error handler and is essentially lost.
Do you have a reproducer?
https://gist.github.com/guss77/f91cd9b857d3be131885b25e79c74fab
Run with Vert.x in the classpath, and provide either "a" or "b" as the arguments to main()
- with "a" it will demonstrate the problem, with "b" it will use a workaround to show how the flow is expected to work.
Steps to reproduce
- With a pending
Future
, attach anonSuccess()
oronFailure()
handler. - Chain some more actions to the resulting
Future
- Add a final
onFailure()
to handle and report any error found - Cause the first handler to throw an unchecked exception.
I would expect the final onFailure
to be called with the exception from the first onSuccess
/onFailure
handler, but it isn't - instead the exception is thrown into the context called onSuccess
/onFailure
- which is likely some executor service that I have no control off.
Extra
There is an easy (but annoying) workaround - replace onSuccess()
with map()
(and remember to return a value - should be the same value as was submitted to the handler, to mimic the behavior of onSuccess()
) or replace onFailure
with recover()
(and remember to return a failed future with the same cause as was submitted to the handler).
When comparing to Java's native CompletableFuture
class, any handler that throws cause the exception to propagate down the chain - there's no such thing as losing exception.
I encountered this issue when converting a set of unit tests from CompletableFuture
to Vert.x Future
and was surprised to find that the idiomatic code below was timing out (if the test fails):
@Test
public void testSomething(VertxTestContext ctx) {
Checkpoint async = ctx.checkpoint();
client.doSomethingAsync()
.onSuccess(result -> {
assertThat(result, is(notNullValue()));
// other asserts
})
.onFailure(ctx::failNow)
.onSuccess(__ -> async.flag());
}