Thursday, October 20, 2016

Scheduling asynchronous task in a Play application

Scheduling of asynchronous task in Play should be done with Akka actors. Play documentation about scheduling is rather laconic. It gives just code fragments and does not elaborate.
This post goes a bit further and shows basic but full example of scheduling of a periodic asynchronous task in a Play.

A basic actor example


1. The HelloAnyone actor prints hello message to the person, which name was sent with the message:
 public class HelloAnyone extends AbstractActor {  
      //Protocol  
      public static class Anyone {  
           private final String name;  
           public Anyone(String name) {  
                this.name = name;  
           }  
           public String getName() {  
                return name;  
           }  
      }  
      private static final String HELLO = "Hello";  
      public static Props props() {  
           return Props.create(HelloAnyone.class);  
      }  
      public HelloAnyone() {  
           receive(ReceiveBuilder  
                .match(Anyone.class, this::sayHello)  
                .matchAny(this::unhandled)  
                .build());  
      }  
      private void sayHello(Anyone anyone) {  
           String helloString = HELLO + anyone.getName();  
           System.out.println(helloString);  
           sender().tell(helloString, self());  
      }  
 }  
2. The actor is created by the controller ActorExamplesController. Method sayHello of the controller implements the ask pattern: it sends a message to the actor and returns the actor reply:
 public class ActorExamplesController extends Controller {  
      private ActorRef helloActor;  
      @Inject  
      public ActorExamplesController(ActorSystem system) {  
           helloActor = system.actorOf(HelloAnyone.props());  
      }  
      public CompletionStage<Result> sayHello(String name) {  
           return FutureConverters.toJava(ask(helloActor, new Anyone(name), 1000))  
                     .thenApply(response-> ok((String)response));  
      }  
 }  
3. The route entry sayHello invokes the controller method:
 GET /sayHello/:name    controllers.ActorExamplesController.sayHello(name)
Each time the URL /sayHello/{some name} is entered the message "Hello {some name}" is returned.

Example of periodic task scheduling


The example implements sending periodic messages to the HelloAnyone actor. The hello message is just logged out in the background. Of course for a real application some significant work shall be done by the scheduled actor.

1. The class HelloScheduler implements an actor, which sends messages to the HelloAnyone actor:
 public class HelloScheduler extends AbstractActor {
  
      // Protocol  
      public static final String CANCEL = "cancel";  
      private static final String TICK = "tick";  
      // The first polling in 30 sec after the start  
      private static final int ON_START_POLL_INTERVAL = 30;  
      private static final int TICK_INTERVAL_SEC = 90;  
      private final ActorRef helloSayer;  
      private final String anyoneName;  
      private Cancellable scheduler;
  
      public static Props props(ActorRef helloSayer, String name) {  
           return Props.create(HelloScheduler.class,  
                     ()->new HelloScheduler(helloSayer, name));  
      }  
      public HelloScheduler(ActorRef helloSayer, String anyoneName) {  
           this.helloSayer = helloSayer;  
           this.anyoneName = anyoneName;  
           receive(ReceiveBuilder  
                .matchEquals(TICK, m -> onTick())  
                .matchEquals(CANCEL, this::cancelTick)  
                .matchAny(this::unhandled)  
                .build());  
      }  
      @Override  
      public void preStart() throws Exception {  
           scheduler = getContext().system().scheduler().scheduleOnce(  
                     Duration.create(ON_START_POLL_INTERVAL, TimeUnit.SECONDS),  
                     self(),  
                     TICK,  
                     getContext().dispatcher(),  
                     null);  
      }  
      @Override  
      public void postRestart(Throwable reason) throws Exception {  
           // No call to preStart  
      }  
      private void onTick() {  
           helloSayer.tell(new Anyone(anyoneName), self());  
           scheduler = getContext().system().scheduler().scheduleOnce(  
                     Duration.create(TICK_INTERVAL_SEC, TimeUnit.SECONDS),  
                     self(),  
                     TICK,  
                     getContext().dispatcher(),  
                     null);  
      }  
      public void cancelTick(String string) {  
           if (scheduler != null) {  
                scheduler.cancel();  
           }  
      }  
 }  
The first message is sent upon creation of a scheduler actor - in the method preStart.
The next message is sent upon arrival of the previous message - in the method onTick.
Each time the result of call to scheduleOnce is assigned to the data member scheduler. This allows to signal about the timer cancellation from the life cycle handler.

2. The class HelloSchedularMonitor manages the life cycle of a scheduler actor:
 public class HelloSchedularMonitor {  
      private ActorRef scheduler;  
      private ActorSystem system;  
      @Inject  
      public HelloSchedularMonitor(ActorSystem system, ApplicationLifecycle lifeCycle) {  
           this.system = system;  
           initStopHook(lifeCycle);  
      }  
      public void startPolling() {  
           scheduler = system.actorOf(HelloScheduler.props(  
                     system.actorOf(HelloAnyone.props()), "Nobody"));            
      }  
      public void cancelTick() {  
           if (scheduler != null) {  
                scheduler.tell(HelloScheduler.CANCEL, null);  
           }  
      }  
      private void initStopHook(ApplicationLifecycle lifeCycle) {  
           lifeCycle.addStopHook(() -> {  
                cancelTick();  
                return CompletableFuture.completedFuture(null);  
           });  
      }  
 }  
3. The class StartupHandler receives in the constructor scheduler monitor and calls to startPolling. The StartupHandler is injected as a singleton object, so a scheduler will be created just once.
 @Singleton  
 public class StartupHandler {  
      @Inject  
      public StartupHandler(final HelloSchedularMonitor schedularMonitor) {  
           schedularMonitor.startPolling();  
      }  
 }  
4. The StartupHandler is registered for injection by the default module Play Module.
 public class Module extends AbstractModule {  
     @Override  
     public void configure() {  
           bind(StartupHandler.class).asEagerSingleton();  
     }  
 }  
Full code examples on Git.

No comments :

About the author

My Photo
I trust only simple code and believe that code should be handsome. This is not a matter of technology, but professional approach, consolidated after years of software development. I enjoy to cause things working and feel very happy, when I manage to solve a problem.
Back to Top