6

Is there a way to wait for a future to complete without blocking the event loop?

An example of a use case with querying Mongo:

Future<Result> dbFut = Future.future();
mongo.findOne("myusers", myQuery, new JsonObject(), res -> {
    if(res.succeeded()) {
      ...
      dbFut.complete(res.result());
    }
    else {
      ...
      dbFut.fail(res.cause());
    }
  }
  });

// Here I need the result of the DB query
if(dbFut.succeeded()) {
  doSomethingWith(dbFut.result());
}
else {
  error();
}

I know the doSomethingWith(dbFut.result()); can be moved to the handler, yet if it's long, the code will get unreadable (Callback hell ?) It that the right solution ? Is that the omny solution without additional libraries ?

I'm aware that rxJava simplifies the code, but as I don't know it, learning Vert.x and rxJava is just too much.

I also wanted to give a try to vertx-sync. I put the dependency in the pom.xml; everything got downloaded fine but when I started my app, I got the following error

maurice@mickey> java \
  -javaagent:~/.m2/repository/co/paralleluniverse/quasar-core/0.7.5/quasar-core-0.7.5-jdk8.jar \
  -jar target/app-dev-0.1-fat.jar \
  -conf conf/config.json 
Error opening zip file or JAR manifest missing : ~/.m2/repository/co/paralleluniverse/quasar-core/0.7.5/quasar-core-0.7.5-jdk8.jar
Error occurred during initialization of VM
agent library failed to init: instrument

I know what the error means in general, but I don't know in that context... I tried to google for it but didn't find any clear explanation about which manifest to put where. And as previously, unless mandatory, I prefer to learn one thing at a time.

So, back to the question : is there a way with "basic" Vert.x to wait for a future without perturbation on the event loop ?

mszmurlo
  • 1,250
  • 1
  • 13
  • 28

2 Answers2

5

You can set a handler for the future to be executed upon completion or failure:

Future<Result> dbFut = Future.future();
mongo.findOne("myusers", myQuery, new JsonObject(), res -> {
    if(res.succeeded()) {
      ...
      dbFut.complete(res.result());
    }
    else {
      ...
      dbFut.fail(res.cause());
    }
  }
  });

dbFut.setHandler(asyncResult -> {
    if(asyncResult.succeeded()) {
      // your logic here
    }
});

This is a pure Vert.x way that doesn't block the event loop

Mohamed Ibrahim Elsayed
  • 2,734
  • 3
  • 23
  • 43
  • 1
    This is an obsolete solution – Vyacheslav Feb 06 '21 at 23:29
  • 2
    Hey Vyacheslav :), if you think it is an outdated solution, please feel free to add a new answer. – Mohamed Ibrahim Elsayed Feb 07 '21 at 22:08
  • instead using future.setHandler, you can use future.onSuccess(value -> {}).onFailure(failure->{}); Also I usually have helper functions like the one either to complete or fail a future I have: FutureUtil.completeOrFail(res.succeeded(), res.result(), res.cause()); or FutureUtil.completeOrFail(res.succeeded(), () -> res.result(), () -> res.cause()); if you can only access res.result() if it succeeded. Also check the Vertx 4 API, they might have incooperated these util functions already. – Martin Kersten Mar 13 '21 at 20:22
3

I agree that you should not block in the Vertx processing pipeline, but I make one exception to that rule: Start-up. By design, I want to block while my HTTP server is initialising.

This code might help you:

/**
 * @return null when waiting on {@code Future<Void>}
 */
@Nullable
public static <T>
T awaitComplete(Future<T> f)
throws Throwable
{
    final Object lock = new Object();
    final AtomicReference<AsyncResult<T>> resultRef = new AtomicReference<>(null);
    synchronized (lock)
    {
        // We *must* be locked before registering a callback.
        // If result is ready, the callback is called immediately!
        f.onComplete(
            (AsyncResult<T> result) ->
            {
                resultRef.set(result);
                synchronized (lock) {
                    lock.notify();
                }
            });

        do {
            // If we get a spurious wake-up before resultRef is set,
            // we need to reacquire the lock, then wait again.
            // Ref: https://stackoverflow.com/a/249907/257299
            // @Blocking
            lock.wait();
        }
        while (null == resultRef.get());
    }

    final AsyncResult<T> result = resultRef.get();
    if (result.failed())
    {
        throw result.cause();
    }

    @Nullable
    final T x = result.result();
    return x;
}
kevinarpe
  • 20,319
  • 26
  • 127
  • 154
  • Do you really need the inner synchronized block? As soon as you come out of `wait()`, possibly due to a spurious wakeup, you are still inside the outer synchronized block, so you are still safe without having to synchronize again. – Lahiru Chandima Aug 02 '22 at 16:21
  • @LahiruChandima I think you are right. Nice suggestion. I will change the code. – kevinarpe Jul 11 '23 at 15:12