I have a Play! application with some tasks I need to run periodically. I can schedule the tasks using Akka, but I am not sure how to start the scheduler itself. What I am doing right now is having a Scheduler object and starting it from Global.scala, like this
// app/jobs/Scheduler.scala
package jobs
import akka.util.duration._
import play.api.libs.concurrent.Akka
import play.api.Play.current
object Scheduler {
def start() {
Akka.system.scheduler.schedule(0 seconds, 1 minutes) {
SomeTask.start()
}
}
}
and then
// app/Global.scala
import play.api._
import jobs.Scheduler
object Global extends GlobalSettings {
override def onStart(app: Application) {
Scheduler.start()
}
}
The problem is that in this, the task runs even in development mode and during tests, that becomes soon very annoying.
Is there a way to schedule jobs with Akka only in production mode?
Methods isProd, isDev and isTest on Play object could be helpful. Even if you doesn't have implicit Application in scope, you can pass it explicitly
override def onStart(app: Application) {
if (isProd(app)) Scheduler.start()
}
Related
I have a restful web service developed using play 2.5 framework.
I want to prime my web service on start by calling itself. This is to make sure that my service is perfectly up and running.
The approach i am taking is using eagerBinding. But the code inside the class injected using eager binding gets executed just before the app starts
Here is what my eagerbinding code looks like
#Singleton
class PrimingMe #Inject()(ws: WSClient) {
isServicePrimed
def isServicePrimed: Boolean = {
println("PRIME ME!!!")
val response = ws.url("http://localhost:9000/index").get
.map {
response =>
response.status match {
case 200 => true
case _ => false
}
}
try {
Await.result(response, 5.second)
} catch {
case _ => false
}
}
}
class ServiceInjectionModule extends AbstractModule {
def configure(): Unit = {
bind(classOf[PrimingMe]).asEagerSingleton
}
}
Inside application.conf
play.modules.enabled += "util.ServiceInjectionModule"
I want to prime my application with a dummy service call so that when the real traffic starts coming all db connections are made. Currently my first api call to the service takes much longer than the usual. What other options do i have to achieve this.
No Purpose is not defeated. Eager loading still works as expected.
Ensure that the util.ServiceInjectionModule is the first module to load by declaring it on the top in the application.conf file.
I have done a small experiment after seeing your question to prove this
This is how my module looks like. It is declared in the root directory and as Module is in the root director i.e app, You do not have to explicitly add to the application.conf
class Module extends AbstractModule {
override def configure() = {
//It is very important for this call to be on the top.
bind(classOf[Initialize]).asEagerSingleton()
}
}
Eager singleton
import com.google.inject.Inject
import com.google.inject.Singleton
import play.api.Logger
import play.api.libs.ws.WSClient
import scala.concurrent.Await
import scala.concurrent.duration.Duration
#Singleton
class Initialize #Inject() (wsClient: WSClient) {
hit()
def hit(): Unit = {
val f = wsClient.url("http://www.google.com").get()
val result = Await.result(f, Duration.Inf)
Logger.info(s"status: ${result.status}")
}
}
output:
[info] application - status: 200
[info] play.api.Play - Application started (Dev)
From the above output you can see that Module has loaded and hit() is called.
I im trying to print "Hello" to console on application start. Can You explain how to do it?
What i tried myself:
app/modules/HelloModule.scala:
package modules
import com.google.inject.AbstractModule
trait Hello {}
class MyHelloClass extends Hello {
initialize() // running initialization in constructor
def initialize() = {
println("Hello")
}
}
class HelloModule extends AbstractModule {
def configure() = {
bind(classOf[Hello])
.to(classOf[MyHelloClass]).asEagerSingleton
}
}
in conf/application.conf i added:
play.modules.enabled += "modules.HelloModule"
and "Hello" is not printed when i run activator run
You need to use Global object, and override "onStart" method:
Defining a Global object in your project allows you to handle global
settings for your application. This object must be defined in the
default (empty) package and must extend GlobalSettings.
import play.api._
object Global extends GlobalSettings {
override def onStart(app: Application) {
Logger.info("Application has started")
}
override def onStop(app: Application) {
Logger.info("Application shutdown...")
}
}
You can also specify a custom GlobalSettings implementation class name
using the application.global configuration key.
Update:
The correct way would be to use Dependency Injection, exactly like it described in the question. GlobalSettings could be removed later
There is no problem with the code in the question. I verified it on my local setup. The code write "Hello" after first request in the development mode "activator run" and after application start in the production mode "activator start".
Btw, try to use some more easy to find string in the log, like
"--------APP DZIABLO HAS BEEN STARTED--------"
It could be so you just missed "Hello" in the log (I did not recognise it from the start)
I'm attempting to schedule an Akka job when my Play 2.2 application starts.
In it's simplest form, this is what my code looks like:
import play.api.Application
import play.api.Play.current
import play.api.GlobalSettings
import play.api.Logger
import play.api.db.DB
import scala.concurrent.duration._
import play.api.libs.concurrent.Akka
import play.api.libs.concurrent.Execution.Implicits._
import scala.slick.driver.MySQLDriver.simple._
object Global extends GlobalSettings {
override def onStart(app: Application) {
lazy val database = Database.forDataSource(DB.getDataSource())
scheduleTheThing(app)
}
private def scheduleTheThing(app: Application) {
Akka.system.scheduler.scheduleOnce(1.minute, new Runnable {
override def run() {
Logger.info("running the thing!")
controllers.Application.runTheThing
}
})
}
}
As you can see, I'm creating a new Runnable that will be executed 1 minute after the application starts. Logger.info("Running the thing!") gets executed just fine... I'm able to see "Running the thing!" in the application log 1 minute after starting the server. However, controllers.Application.runTheThing doesn't seem to get called:
object Application extends Controller {
def runTheThing = DBAction {
implicit session => {
Logger.info("It's happening!")
DBThing.doSomeDBStuff()
}
}
// ...
}
My "It's happening!" log statement never shows up, and the stuff that DBThing.doSomeDBStuff() is supposed to do never happens. The curious thing is that there are no errors in the console or log files.
How is one supposed to call a controller DBAction from a scheduled runnable? Or How should I rework my scheduler so that this works? Any help would be greatly appreciated, thanks.
Calling the action method will do nothing but return an instance of Action, to actually execute it you would have to call it like play normally does with a request. I would recommend to extract this to non-controller code and call that from your scheduled task instead.
In Play 1 it was simply:
#Every(value = "1h")
public class WebsiteStatusReporter extends Job {
#Override
public void doJob() throws Exception {
// do something
}
}
What is the equivalent for Play 2.1?
I know that Play uses akka and I found this code sample:
import play.api.libs.concurrent.Execution.Implicits._
Akka.system.scheduler.schedule(0.seconds, 30.minutes, testActor, "tick")
But being a Scala noob I don't understand how works. Can someone provide a complete, working example (end to end)?
Here is an extract from a code of mine:
import scala.concurrent.duration.DurationInt
import akka.actor.Props.apply
import play.api.Application
import play.api.GlobalSettings
import play.api.Logger
import play.api.Play
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.libs.concurrent.Akka
import akka.actor.Props
import actor.ReminderActor
object Global extends GlobalSettings {
override def onStart(app: Application) {
val controllerPath = controllers.routes.Ping.ping.url
play.api.Play.mode(app) match {
case play.api.Mode.Test => // do not schedule anything for Test
case _ => reminderDaemon(app)
}
}
def reminderDaemon(app: Application) = {
Logger.info("Scheduling the reminder daemon")
val reminderActor = Akka.system(app).actorOf(Props(new ReminderActor()))
Akka.system(app).scheduler.schedule(0 seconds, 5 minutes, reminderActor, "reminderDaemon")
}
}
It simply starts a daemon at the start of the app, and then, every 5 minutes. It uses Play 2.1 and it works as expected.
Note that this code uses the Global object which allows to run some code on application startup.
An example:
case object Tick
class TestActor extends Actor {
def receive = {
case Tick => //...
}
}
val testActor = Akka.system.actorOf(Props[TestActor], name = "testActor")
Akka.system.scheduler.schedule(0.seconds, 30.minutes, testActor, Tick)
Take a look into Akka's doc
sample you gave is:
def schedule(
initialDelay: Duration,
frequency: Duration,
receiver: ActorRef,
message: Any): Cancellable
Means: start 0 seconds from now, at every 30 minutes send to the actor testActor message Tick
what's more for simple tasks you probably don;t need to use Actors - you can just schedule the new Runnable:
def schedule(
initialDelay: Duration, frequency: Duration, runnable: Runnable): Cancellable
More detailed description in other answer
A simple play scheduler without using Actors.
It can be done using org.quartz.scheduler and calling the scheduler from the Global class.
Sample scheduler
I need to execute a code allowing the launch of scheduled jobs on start of the application, how can I do this? Thanks.
Use the Global object which - if used - must be defined in the default package:
object Global extends play.api.GlobalSettings {
override def onStart(app: play.api.Application) {
...
}
}
Remember that in development mode, the app only loads on the first request, so you must trigger a request to start the process.
Since Play Framework 2.6x
The correct way to do this is to use a custom module with eager binding:
import scala.concurrent.Future
import javax.inject._
import play.api.inject.ApplicationLifecycle
// This creates an `ApplicationStart` object once at start-up and registers hook for shut-down.
#Singleton
class ApplicationStart #Inject() (lifecycle: ApplicationLifecycle) {
// Start up code here
// Shut-down hook
lifecycle.addStopHook { () =>
Future.successful(())
}
//...
}
import com.google.inject.AbstractModule
class StartModule extends AbstractModule {
override def configure() = {
bind(classOf[ApplicationStart]).asEagerSingleton()
}
}
See https://www.playframework.com/documentation/2.6.x/ScalaDependencyInjection#Eager-bindings
I was getting a similar error.
Like #Leo said, create Global object in app/ directory.
Only thing I had to make sure was to change "app: Application" to "app: play.api.Application".
app: Application referred to class Application in controllers package.