Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ListenableFuture callback execution order

Guava's ListenableFuture library provides a mechanism for adding callbacks to future tasks. This is done as follows:

ListenableFuture<MyClass> future = myExecutor.submit(myCallable);
Futures.addCallback(future, new FutureCallback<MyClass>() {
    @Override
    public void onSuccess(@Nullable MyClass myClass) {
      doSomething(myClass);
    }

    @Override
    public void onFailure(Throwable t) {
      printWarning(t);
    }}, myCallbackExecutor);
}

You can wait for a ListenableFuture to complete by calling its get function. For instance:

MyClass myClass = future.get();

My question is, are all callbacks for a certain future guaranteed to run before the get terminates. I.e. if there is a future with many callbacks on many callback executors registered, will all the callbacks complete before get returns?

Edit

My use case is, I pass a builder around to many classes. Each class populates one field of the builder. I want all fields to be populated asynchronously because each field requires an external query to generate the data for the field. I want the user who calls my asyncPopulateBuilder to receive a Future on which she can call get and be assured that all the fields have been populated. The way I thought to do it is as follows:

final Builder b;
ListenableFuture<MyClass> future = myExecutor.submit(myCallable);
Futures.addCallback(future, new FutureCallback<MyClass>() {
  @Override
  public void onSuccess(@Nullable MyClass myClass) {
    b.setMyClass(myClass);
  }

  @Override
  public void onFailure(Throwable t) {
    printWarning(t);
  }}, myCallbackExecutor);
}
// Do the same thing for all other fields.

What is the recommended way to block until all fields are populated in such a case?

like image 410
Benjy Kessler Avatar asked Sep 24 '15 12:09

Benjy Kessler


1 Answers

Callbacks are not guaranteed to run before get returns. More on that below.

As for how to address this use case, I would suggest turning the query for each field's data into a separate Future, combining them with allAsList+transform, and taking action on that. (We may someday provide a shortcut for the "combine" step.)

ListenableFuture<MyClass> future = myExecutor.submit(myCallable);

final ListenableFuture<Foo> foo =
    Futures.transform(
        future,
        new Function<MyClass, Foo>() { ... },
        myCallbackExecutor);
final ListenableFuture<Bar> bar = ...;
final ListenableFuture<Baz> baz = ...;

ListenableFuture<?> allAvailable = Futures.allAsList(foo, bar, baz);
ListenableFuture<?> allSet = Futures.transform(
    allAvailable, 
    new Function<Object, Object>() {
      @Override
      public Object apply(Object ignored) {
        // Use getUnchecked, since we know they already succeeded:
        builder.setFoo(Futures.getUnchecked(foo));
        builder.setFoo(Futures.getUnchecked(bar));
        builder.setFoo(Futures.getUnchecked(baz));
        return null;
      }
    }
};

Now the user can call allSet.get() to await population.

(Or maybe you want for allSet to be a Future<Builder> so that the user is handed a reference to the builder. Or maybe you don't need a full-on Future at all, only a CountDownLatch, in which you could use addCallback instead of transform and count down the latch at the end of the callback.)

This approach may also simplify error handling.


RE: "Do callbacks run before get?"

First, I am pretty sure that we don't guarantee this anywhere in the spec, so thanks for asking rather than just going for it :) If you do end up wanting to rely on some behavior of the current implementation, please file an issue so that we can add documentation and tests.

Second, if I take your question very literally, what you're asking for isn't possible: If get() waits for all listeners to complete, then any listener that calls get() will hang!

A slightly more lenient version of your question is "Will all the listeners at least start before get() returns?" This turns out to be impossible, too: Suppose that I attach two listeners to the same Future to be run with directExecutor(). Both listeners simply call get() and return. One of the listeners has to run first. When it calls get(), it will hang, since the second listener hasn't started yet -- nor can it until the first listener is done. (More generally, it can be dangerous to rely on any given Executor to execute a task promptly.)

A still more lenient version is "Will the Future at least call submit() for each of the listeners before get() returns?" But this ends up with a problem in the same scenario as I just described: Calling submit(firstListener) on a directExecutor() runs the task and calls get(), which can't complete until the second listener is started, which can't happen until the first listener completes.

If anything, it's starting to sound much more likely that get() will return before any listeners execute. But thanks to the unpredictability of thread scheduling, we can't rely on that, either. (And again: It's not documented, so please don't rely on it unless you ask for it to be documented!)

like image 111
Chris Povirk Avatar answered Nov 15 '22 08:11

Chris Povirk