1

I want to create a scheduled task in Play 2.5. I found some resources related to this topic but none of them were for Play 2.5. I found out this resource related to what I am looking for and it looks good. Also on the same link there is a migration guide from 2.4 to 2.5.

The examples from older versions used GlobalSettings as base but this was deprecated in 2.5. The migration guide is important because it says that we should use dependency injection instead of extending this trait. I am not sure how to do that.

Can you give me some guidance?

tzortzik
  • 4,993
  • 9
  • 57
  • 88

2 Answers2

9

You need to run sheduled task inside Akka Actor:

SchedulerActor.scala

package scheduler

import javax.inject.{Inject, Singleton}

import akka.actor.Actor
import org.joda.time.DateTime
import play.api.Logger

import scala.concurrent.ExecutionContext

@Singleton
class SchedulerActor @Inject()()(implicit ec: ExecutionContext) extends Actor {
  override def receive: Receive = {
    case _ =>
      // your job here
  }
}

Scheduler.scala

package scheduler

import javax.inject.{Inject, Named}

import akka.actor.{ActorRef, ActorSystem}
import play.api.{Configuration, Logger}

import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

class Scheduler @Inject() (val system: ActorSystem, @Named("scheduler-actor") val schedulerActor: ActorRef, configuration: Configuration)(implicit ec: ExecutionContext) {
  val frequency = configuration.getInt("frequency").get
  var actor = system.scheduler.schedule(
    0.microseconds, frequency.seconds, schedulerActor, "update")

}

JobModule.scala

package modules

import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
import scheduler.{Scheduler, SchedulerActor}

class JobModule extends AbstractModule with AkkaGuiceSupport {
  def configure() = {
    bindActor[SchedulerActor]("scheduler-actor")
    bind(classOf[Scheduler]).asEagerSingleton()
  }
}

application.conf

play.modules.enabled += "modules.JobModule"
mgosk
  • 1,874
  • 14
  • 23
  • For me this works only locally unfortunately. When I package the app with `sbt stage` and deploy to a server, it won't start up: `Oops, cannot start the server. Cannot load module` [...] `Caused by: java.lang.ClassNotFoundException: modules.JobModule`. Am I missing something? Thanks for any help. – Nick Apr 21 '17 at 12:35
  • I tested it only with packages builded with sbt native packager and it works like a charm. I will try look into your problem during weekend. – mgosk Apr 21 '17 at 14:58
  • Your solution works indeed in production too. It seems there was something wrong with Azure's automated deployment (I hate it). Thank you!! – Nick Apr 24 '17 at 16:01
0

If you don't want to use akka, you can use java:

  • ScheduledFuture
  • ScheduledExecutorService

DemoDaemon.scala:

import java.util.concurrent.{Executors, ScheduledFuture, TimeUnit}
import javax.inject._
import play.Configuration
import scala.util.Try

class DemoDaemon @Inject() (conf: Configuration) {

  val isEnabled = conf.getBoolean("daemon.enabled")
  val delay = conf.getLong("daemon.delay")

  private var scheduledTaskOption : Option[ScheduledFuture[_]] = None

  def task(): Unit = {

    Try {
      println("doSomething")
    } recover {
      case e: Throwable => println(e.getMessage)
    }
  }

  def start(): Unit = {
    if (isEnabled) {
      val executor = Executors.newScheduledThreadPool(1)

      scheduledTaskOption = Some( 
        executor.scheduleAtFixedRate(
          new Runnable {
            override def run() = task()
          },
          delay, delay, TimeUnit.SECONDS
        )
      )
    } else {
      println("not enabled")
    }
  }

  def stop(): Unit = {
    scheduledTaskOption match {
      case Some(scheduledTask) =>
        println("Canceling task")
        val mayInterruptIfRunning = false
        scheduledTask.cancel(mayInterruptIfRunning)
      case None => println("Stopped but was never started")
    }
  }
}

DaemonService.scala

import javax.inject.Inject
import play.api.inject.ApplicationLifecycle
import scala.concurrent.Future

class DaemonService @Inject() (appLifecycle: ApplicationLifecycle, daemon: DemoDaemon) {

  daemon.start()

  appLifecycle.addStopHook{ () =>
    Future.successful(daemon.stop())
  }
}

JobModule.scala

import com.google.inject.AbstractModule

class JobModule extends AbstractModule {
  def configure(): Unit = {
    bind(classOf[DaemonService]).asEagerSingleton()
  }
}

application.conf

daemon.enabled = true
daemon.delay = 10
play.modules.enabled += "com.demo.daemon.JobModule"
Remi Thieblin
  • 176
  • 1
  • 5