Skip to content

Future.onSuccess() and Future.onFailure() do not propagate exceptions in handlers #4455

@guss77

Description

@guss77

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

  1. With a pending Future, attach an onSuccess() or onFailure() handler.
  2. Chain some more actions to the resulting Future
  3. Add a final onFailure() to handle and report any error found
  4. 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());
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions