[2.0-Java] Async external requests without locking up your application (with possible solution!)

185 views
Skip to first unread message

Alex North

unread,
May 27, 2012, 12:53:55 AM5/27/12
to play-fr...@googlegroups.com
My team has made a few attempts at implementing async requests to external services and tripped up a few times. I think I've finally figured out how to do it, so want to share my findings to validate them and suggest improvements so that others needn't fall over the same hurdles.

Let's say your play controller makes a request to some external or deferred service. It's not a web service, so you can't use the WS API, it's just some bit of code that's going to call you back later when it has a result from wherever it's getting it. In our case this result is available as a Guava ListenableFuture<T>.

Obviously wrong is to call ListenableFuture#get() in the controller. This blocks a controller thread, of which you have few (by default, 1 per CPU, though you've probably already bumped that up after your app first locked up in production). For this to scale you'd need one thread per request and the whole point of async is to avoid that.

Next you might have been tempted by play.libs.Akka#future(Callable<T> callable), with the callable invoking the Future's get(). While this does avoid blocking a controller thread unfortunately it just passes the problem on to the "default" Akka dispatcher. This also has a small, limited number of threads by default and again will lock up your app once you run out. IMO Akka#future(Callable) should come with a big warning that the callable shouldn't block.

My latest attempt, which I think might actually work, is to construct an akka.dispatch.Future to pass to Akka#asPromise(Future<T>) in the hope that Play will attach a callback to eventually satisfy a controller's AsyncResult when the result is available rather than invoking any of the blocking methods and locking up a thread. I discovered akka.dispatch.DefaultPromise and so my hookup looks like: 

  /**
   * Transforms a Guava ListenableFuture to a Play F.Promise which is satisfied when the
   * future is satisfied.
   */
  public static <T> F.Promise<T> promise(final ListenableFuture<? extends T> listenableFuture) {
    final Promise<T> p = emptyPromise();
    // The callback will run in a same-thread executor, which might mean the calling
    // (probably controller) thread if the future is already done, or the thread satisfying
    // the future. In either case the real work will subsequently be done in the Akka system
    // dispatcher.
    Futures.addCallback(listenableFuture, new FutureCallback<T>() {
      @Override
      public void onSuccess(T result) {
        p.success(result);
      }

      @Override
      public void onFailure(Throwable t) {
        p.failure(t);
      }
    });
    return Akka.asPromise(p);
  }

  /** Creates an unsatisfied promise in the Akka system context. */
  private static <T> Promise<T> emptyPromise() {
    return new DefaultPromise<T>(Akka.system().dispatcher());
  }

You can imagine Futures.addCallback being replaced with any of a range of ways one might receive a result "later".

I wonder if any of the Play engineers could confirm for me that this actually works. That is, does passing an Akka Future to Akka#asPromise() give the framework what it needs such that it won't attempt a blocking call to await the result? If not, how is one supposed to achieve that?

Alex North

unread,
May 27, 2012, 1:02:24 AM5/27/12
to play-fr...@googlegroups.com
P.S. Good reading for more background is https://groups.google.com/d/topic/play-framework/T5Vk7aop72E/discussion, but "use the WS API" is not an appropriate solution for many cases.
Reply all
Reply to author
Forward
0 new messages