[play-2.3.x java] Blocking on webservice calls and using ExecutionContext

99 views
Skip to first unread message

Brian T. Grant

unread,
Mar 27, 2015, 6:01:21 PM3/27/15
to play-fr...@googlegroups.com
Greetings,

I have a somewhat unconventional use case for which I'm seeking advice. I already have an implementation that made sense to me based on the Play documentation and the exploration work that I did. During the code review one of my colleagues had some questions that exposed some assumptions I had made.

We have a shiny, new Play 2.3 application that makes use of WS/async for all calls. We also have a Play 1.2 application that generally blocks and does not make use of WS/async/Promises, etc. We're migrating the APIs from the Play 1 project to the Play 2 project this year and, in an attempt to simplify this migration, I've introduced an adapter to our code which will allow the code we're migrating to make a blocking call by doing:
public T makeBlockingCall(inputs) {
  Promise<T> responsePromise = makeWsCallUsingPlayLibsWs(inputs);
  return responsePromise.get(SOME_ARBITRARILY_LARGE_VALUE);
}

Since the call to responsePromise.get will block, I want to make sure that we don't tie up members of Play's default thread pool, so I have set up a new one following the Play documentation.

My thinking was that, in order to ensure that Play's default threads are returned quickly for each request that uses these blocking operations, we would do:
public static F.Promise<Result> someActionMethod() {
  final F.Promise<String> someArbitraryValueFromTheRequest = F.Promise.pure(extractAValueFromTheRequest(request());

  return someArbitraryValueFromTheRequest.map(new F.Function<String, Result>() {
    @Override
    public Result apply(String s) throws Throwable {
      return performBlockingOperation(s);
    }
  }, getExecutionContextForBlockingOperations());
}

This would result in someArbitraryValueFromTheRequest being fulfilled on the thread pool which has been created for this purpose; done and done. This is where my colleague's thinking and mine diverge.

Inside of makeWsCallUsingPlayLibsWs(inputs), we do a number map operations which use promise.map(F.Function<T> someFunction) for boilerplate stuff like logging elapsed time, error-handling and the like. My assumption was that since the Play WS is async, it's safe to allow it to use the default thread pool since that thread won't be tied up in the same way the thread from the blocking pool will be. His thinking is that we'd have to use the same ExecutionContext all the way down.

The code samples above illustrate my thinking. I guess his would look like this:
public T makeBlockingCall(inputs, blockingContext) {
  Promise<T> responsePromise = makeWsCallUsingPlayLibsWs(inputs, blockingContext);
  return responsePromise.get(SOME_ARBITRARILY_LARGE_VALUE);
}

private Promise<T>
makeWsCallUsingPlayLibsWs(inputs, blockingContext) {
  F.Promise<T> response = WS.url(urlFromInputs).get()
    .map(ELAPSED_TIME_FUNCTION, blockingContext)
    .map(BOILERPLATE_ERROR_HANDLING_FUNCION, blockingContext)
    .map(WSRESPONSE_TO_T_FUNCTION, blockingContext);

  return response;
}


Since this post is long enough already, I'll leave it at that. If there are questions/comments (other than, "just don't do it that way") I'll answer whatever I can.

Thank you!
Brian

Brian T. Grant

unread,
Apr 6, 2015, 1:13:24 PM4/6/15
to play-fr...@googlegroups.com
No suggestions? If someone has some suggestions about how I could so some additional research to figure out the correct approach on my own, I'm open to that as well.

amertum

unread,
Apr 6, 2015, 5:19:36 PM4/6/15
to play-fr...@googlegroups.com
A thread is blocking if it does a long computation or a long waiting.

So, if calculation that is done within map are quick, it does not 'block'. So you can safely execute them in the default thread pool IMHO.

Manuel Bernhardt

unread,
Apr 7, 2015, 6:29:23 AM4/7/15
to play-fr...@googlegroups.com
You don't need to use the "blocking" execution context when you do purely async work, e.g. using the WS library, because, as you say, it is purely asynchronous.

The only thing to be aware of is the cost of context switching. If you move from one EC to another you'll pay the price of context switching. James talked about this at the Ping Conference in Budapest, check the video here: http://www.ustream.tv/recorded/42801712

Hope this helps,

Manuel

On Mon, Apr 6, 2015 at 11:19 PM, amertum <ame...@gmail.com> wrote:
A thread is blocking if it does a long computation or a long waiting.

So, if calculation that is done within map are quick, it does not 'block'. So you can safely execute them in the default thread pool IMHO.

--
You received this message because you are subscribed to the Google Groups "play-framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framewor...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Brian T. Grant

unread,
Apr 13, 2015, 12:02:24 PM4/13/15
to play-fr...@googlegroups.com
I'm calling F.Promise<WSResponse>.get() and that Promise was produced by making web service calls using play.libs.ws. There aren't "calculations" per se and the underlying operations through use of play.lib.ws are non-blocking. Waiting on F.Promise<WSResponse>.get() would block the thread that's calling it (wouldn't it?) and these calls are not guaranteed to be returned quickly; our default timeout on web serivce calls is ~30 seconds. I'm making the call to F.Promise<WSResponse>.get() using a non-default ExecutionContext that much seems to be "the right thing to do".

My colleague's question is whether that same, custom ExecutionContext needs to be used when performing map operations on the F.Promise<WSResponse> before get() is called. I'm sure it wouldn't hurt to further pass the ExecutionContext around, but I'd rather not do so unnecessarily. :-)
Message has been deleted

Will Sargent

unread,
Apr 13, 2015, 2:01:13 PM4/13/15
to play-fr...@googlegroups.com
Play WS doesn't use the default thread pool -- it's a wrapper on top of AsyncHttpClient, and so uses a Netty thread pool internally that is 2x the number of cores on the machine by default.


If you have long running operations, you shouldn't use the default thread pool, because you could cut down on the number of requests you can process.  The NYT ran into this not so long ago.




Will Sargent
Consultant, Professional Services
Typesafe, the company behind Play Framework, Akka and Scala

Brian T. Grant

unread,
Apr 14, 2015, 9:48:48 AM4/14/15
to play-fr...@googlegroups.com
Thank you, Will!

I wasn't aware that WS calls used their own thread pool. If that's the case then it doesn't seem to make a lot of sense to pass the custom ExecutionContext to it because it's already not using the default and other than F.Promise<WSResponse>.get() potentially blocking the request thread, the thread redeeming the promise won't be blocked.

I'll dig into the links you provided when I get a chance.

Brian

--
You received this message because you are subscribed to a topic in the Google Groups "play-framework" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/play-framework/FiB_NoUQ9TI/unsubscribe.
To unsubscribe from this group and all its topics, send an email to play-framewor...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages