Our application has a number of services that implement ListenableFuture-based APIs, to the tune of:
public interface MyService {
ListenableFuture<Thing> getMyThing();
ListenableFuture<?> putMyThing(Thing thing);
}
As our domain model is not at all thread-safe, we run most of our code, except for the aforementioned services, on a well-known single-thread Executor. I think it would be nice if the services could guarantee that any listener added to the Futures they produce will be called on that Executor.
Of course, I could very well enforce this in the services’ clients, by calling ListenableFuture.addListener, Futures.addCallback or Futures.transform with the appropriate Executor argument but what I’m aiming at is precisely reducing the complexity and possibility of errors in the client code, so I would like the listener calls to happen on the well-known Executor when those methods are called without passing an Executor argument.
So, for now I’ve been implementing the services’ methods this way:
class MyServiceImpl {
private Executor executor; /* the "main" executor */
public ListenableFuture<Thing> getMyThing() {
ListenableFuture<Thing> future = ...; /* actual service call */
return Futures.transform(future, Functions.<Thing>identity(), executor );
}
}
First, does this even work? From the Guava source, it seems that it does, but I would be glad for some kind of confirmation, and I’m having a bit of a hard time thinking about unit testing this.
Moreover, I’m a bit worried about the usefulness/cost ratio of the whole “Service calls back on specified thread (by default)” pattern. Does anyone have experience with something like this? Are there any hidden pitfalls in this approach?
Your proposed
identity()solution should work — with two exceptions.Proof: First, notice that the only reasonable way for
Futures.transformto learn when its input is done is through a call toinput.addListener(). We know thataddListener()respects the given executor. (If you don’t believe me:ListenableFutureTaskusesExecutionList, which invokes listeners in only two places:add()andexecute(). In both places, it uses the given executor.) Thus, any task run at the time that the inputFuturecompletes will run in the given executor.The key phrase here is “run at the time that the input
Futurecompletes.” The exceptions:Futureis complete, it will run in the thread that callsaddListener.Future(as opposed to the original), the listeners will run in the thread that callscancel.Depending on how your application is structured, these exceptions might be OK, but they’re something to keep in mind. If they are a problem, you may want to use a
ForwardingListenableFuturesolution (like eneveu’s but apparently less strict).