Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Long Running Process with Progress Bar Example PlayFramework 2

I am trying to implement a long running background process that is spawned when a user visits a page. I would like to display the progress of the task just like in this example: http://web.archive.org/web/20130122091205/http://www.lunatech-research.com/archives/2011/10/31/progressbar-jqueryui-websockets-playframework

Does anyone know of a tutorial for PlayFramework 2.0 (using inbuilt AKKA) ? This one is for 1.2

like image 932
Ciaran Fisher Avatar asked Jul 09 '12 15:07

Ciaran Fisher


1 Answers

After reading all the Akka Documentation for Java http://doc.akka.io/docs/akka/2.0.1/intro/getting-started-first-java.html i've come up with this which seems to work well.

The system works by first creating a unique Actor to process a "report" (when the generate page is loaded). This actor spawns a child actor which reports its progress back to the parent. The parent actor is then polled via JavaScript for the status of the child thread.

Once the child has finished it is terminated and once the Parent detects the child is finished it terminates itself.

Below is all the code, feel free to tear me apart if i've gone about this the wrong way! (Is it OK to store state in Actors?!?)

The controller code:

public class Application extends Controller {


public static Result generateReport()
{
    //create akka job

    //we should really create the actor with UUID name so that someone can't guess
    //and use the API to view the status of other peoples jobs, it be fairly easy
    //to guess as it goes $a,$b,$c etc...
   ActorRef myActor = Akka.system().actorOf(new Props(MyGeneratorMaster.class));

   System.out.println( myActor.path());
    myActor.tell(new ConfigMessage("blarg message"));

    return ok(generating.render("blarg","title",myActor.path().name()));
}

public static Result status(String uuid)
{
    uuid =  "akka://application/user/"+uuid;
    ActorRef myActor = Akka.system().actorFor(uuid);

    if(myActor.isTerminated())
    {
               return ok("Report Generated - All Actors Terminated") ;
    }
    else
    {

        return async(
                Akka.asPromise(ask(myActor,new StatusMessage(), 3000)).map(
                        new F.Function<Object,Result>() {
                            public Result apply(Object response) {

                                if(response instanceof ResultMessage)
                                {
                                    return ok(((ResultMessage) response).getResult());
                                }
                                return ok(response.toString());
                            }
                        }
                )
        );

    }
}

The Master Actor:

public class MyGeneratorMaster extends UntypedActor {

    private int completed = 0;

    @Override
    public void postStop() {
        super.postStop();   
        System.out.println("Master Killed");
    }

    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof actors.messages.ConfigMessage) {
            ConfigMessage config = (ConfigMessage) message;

            System.out.println("Received Config:" + config.getConfig());

            //Need to spawn child actor here..
            ActorRef child = this.getContext().actorOf(new Props(MyGeneratorChildWorker.class));

            //make the child thread do stuff
            child.tell(new ConfigMessage("doSomething!"));

            child.tell(akka.actor.PoisonPill.getInstance());//kill the child after the work is complete...

        } else if (message instanceof StatusUpdate) {
            System.out.println("Got Status Update");
            completed = ((StatusUpdate) message).getProgress();
        } else if (message instanceof StatusMessage) {
            System.out.println("Got Status Message");
            getSender().tell(new ResultMessage("Status: " + completed + "%"), getSelf());

            if(completed == 100)
            {
                //kill this actor, we're done!
                //could also call stop...
                this.getSelf().tell(akka.actor.PoisonPill.getInstance());
            }
        } else {
            System.out.println("unhandled message"+message.toString());
            unhandled(message);
        }

    }
}

The Child Actor:

public class MyGeneratorChildWorker extends UntypedActor {

    @Override
    public void postStop() {
        super.postStop();    
        System.out.println("Child Killed");
    }

    @Override
    public void onReceive(Object message) throws Exception {

        if (message instanceof ConfigMessage) {

            System.out.println("Created Child Worker");

            System.out.println("Doing Work:");
            try {

                for (int i = 0; i <= 100; i++) {


                    //update parent
                    this.context().parent().tell(new StatusUpdate(i));
                    long j = 1;
                     //waste loads of cpu cycles
                    while (j < 1E8) {
                        j = j + 1;
                    }
                }
            } catch (Exception ex) {

            }
            System.out.println("Done Work:");


        } else
            unhandled(message);
    }
}

The view page with the long polling JavaScript:

@(message: String)(title: String)(id: String)@main(title) {

<h2>@message</h2>

        <script type="text/javascript">

            function getPercentage()
            {

                $.ajax({
                    type: "GET",
                    url: "/status/@id",
                    dataType: "html",
                    success: function(html)
                        {
                        $('#status').html(html);


                        }
                });

            }

            $(document).ready(function() {


            setInterval("getPercentage()",100);
            });



        </script>

        <div id="status">

        </div>

}
like image 106
Ciaran Fisher Avatar answered Oct 23 '22 04:10

Ciaran Fisher