I am trying to create a way to handle blocking operations in a specific way in Play. First I have described what my aim is followed by what I have managed so far. Can you please tell me if I am on the right track - if so, could you help me understand how to complete the code? If it is not the right way to do it could you suggest a better alternative? Thanks a lot for all your help
Aim: Would like to have all blocking operations sent to one thread to a separate thread to be handled asynchronously. New requests that come in are not to take up more threads but instead place them in a queue (or anything similar) to be handled by the single thread. For each item that is processed asynchronously by the extra thread, some text must be gathered and returned to the browser.
So after reading docs and SO questions it appears that actors must be used. I like the concept of actors but have never used them before so am still learning. This is what I have:
package models;
import java.io.*;
import play.mvc.*;
import play.libs.*;
import play.libs.F.*;
import akka.actor.*;
public class ActorTest extends UntypedActor {
static BufferedReader reader = new BufferedReader(new InputStreamReader(
System.in));
@Override
public void onReceive(Object message) throws Exception {
if (message instanceof String) {
getSender().tell(
"You sent me " + ((String) message)
+ " and the consol replied with "
+ reader.readLine(), getSelf());
} else
unhandled(message);
}
}
As you can see the blocking operation is readLine() - just an way of testing.
Is this how it should be done? If so, I had assumed that from the controller, I some how create an async result or something using promises. [ Handling asynchronous results ].
Couple issues, how do I send a message to the Actor and get the reply? I mean can I get the result from a tel() call? How do I make sure that more threads don't get taken up and that all operations go into a queue - or is this already handled by the actor?
Could you please provide an example controller action that could do this?
Your help is greatly appreciated.
PS FYI I am really new to all this so just to get to this stage I have found these docs useful to read - the Akka actor pages, play of course and some wiki pages on actors.
[edit] sorry I said single thread but it could be a thread pool - just as long as only the assigned thread / thread pool is used to handle the blocking io not any others.
You can send a message to the Akka actor using ask (instead of tell). It will return to you a Future, which then you can map to a Promise<Result>
.
However, you don't really need to use Akka if you don't have to. You can simply use Futures/Promises to run your blocking operation in the background.
In either approach, you end up with a Future from which you can complete the request when the future finishes.
...
import play.libs.F.*;
public static Promise<Result> index() {
Promise<Integer> promiseOfInt = Promise.promise(
new Function0<Integer>() {
public Integer apply() {
// long-running operation (will run in separate thread)
return 42;
}
});
return promiseOfInt.map(
new Function<Integer, Result>() {
public Result apply(Integer i) {
// 'i' is the result after Promise is complete
return ok("Got result: " + i);
}
});
}
If you're using Akka, you need to convert the Future
returned from ask
to Play's Promise
as follows:
public static Promise<Result> index() {
ActorRef myActor = Akka.system().actorFor("user/my-actor");
return Promise.wrap(ask(myActor, "hello", 1000)).map(
new Function<Object, Result>() {
public Result apply(Object response) {
return ok(response.toString());
}
});
}
...
import play.libs.F.*;
public static Result index() {
Promise<Integer> promiseOfInt = play.libs.Akka.future(
new Callable<Integer>() {
public Integer call() {
// long-running operation (will run in separate thread)
return 42;
}
});
return async(
promiseOfInt.map(
new Function<Integer,Result>() {
public Result apply(Integer i) {
// 'i' is the result after Promise is complete
return ok("Got result: " + i);
}
}));
}
If you're using Akka, you need to convert the Future
returned from ask
to Play's Promise
as follows:
public static Result index() {
ActorRef myActor = Akka.system().actorFor("user/my-actor");
return async(
Akka.asPromise(ask(myActor,"hello", 1000)).map(
new Function<Object,Result>() {
public Result apply(Object response) {
return ok(response.toString());
}
}
)
);
}