Calling Controller DBAction with scheduled Akka Runnable (in Scala / Play 2.2) - scala

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.

Related

Failed on Injecting Akka Actor to Play Framework 2.5 with Guice

I am trying to use an Akka actor in my Play 2.5 application with dependency injection. I basically followed the documentation on that, and here's a glimpse of my code:
The Actor
Basically, I have a simple actor that receive some data, process it, and send it back to the caller.
package actors
import akka.actor._
import akka.pattern.{AskTimeoutException, ask}
import akka.util.Timeout
import javax.inject._
import com.google.inject.assistedinject.Assisted
import play.api.Configuration
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
object ServiceActor {
val ConfigKey = "actors.path" // remote actor path
trait Factory {
def apply(key: String): Actor
}
}
class ServiceActor #Inject()(configuration: Configuration,
actorSystem: ActorSystem,
#Assisted key: String)
(implicit val ec: ExecutionContext)
extends Actor {
import ServiceActor._
implicit val timeout = Timeout(5.minutes)
def receive = {
case data: SomeData => sender ! someFunction(data)
}
// implemented in the real code, omitted because not important for the question
def someFunction = ???
}
The Module
As per documentation, I need to make a simple module, and activate it. Here's the module:
package modules
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
import actors.ServiceActor
class ServiceModule extends AbstractModule with AkkaGuiceSupport {
def configure = {
bindActor[ServiceActor]("service-actor")
}
}
And then I activate this module by adding the following line to application.conf:
play.modules {
enabled += modules.ServiceModule
}
The Controller
Finally, I try to use the actor in a controller:
package controllers
import akka.actor._
import akka.pattern.ask
import akka.util.Timeout
import javax.inject._
import play.api.mvc._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext
#Singleton
class SearchController #Inject()(#Named("service-actor") serviceActor: ActorRef)
(implicit ec: ExecutionContext)
extends Controller {
implicit val timeout = Timeout(5.minutes)
def search(param: String) = Action.async {
val request = SomeRequestType(param)
(serviceActor ? request).mapTo[SomeResponseType] map { result =>
Ok(result.toString)
}
}
}
As far as I can understand, I have followed the documentation perfectly, but calling the controller give me this error:
[AskTimeoutException: Recipient[Actor[akka://application/user/service-actor#-366470383]]
had already been terminated.
Sender[null] sent the message of type "some.namespace.SomeRequestType".]
As far as I can understand, the error is about how the actor is a null, and we can't send message to it. While the error code said "had already been terminated", it looks like the actor's never been initiated in the first place.
I'm not sure where to look first, and what am I doing wrong as I have no experience in using Guice.
The question is, what am I doing wrong? Why didn't the actor initiated, or if it is indeed already terminated, why is it terminated? How do I fix this?
Thank you very much.
Hope you have already solved the issue. However I am still posting answers here just for some new comers' reference since it took me a while to figure out the cause of this issue.
As far as I can understand, the error is about how the actor is a null, and we can't send message to it.
Not exactly. Ask pattern ? has an implicit parameter of sender, which defaults to be ActorRef.noSender, the source code is here.
Normally, if you're inside an Actor you have an implicit ActorRef in scope called self, but since you're not in an Actor it's just taking the default.
While the error code said "had already been terminated", it looks like the actor's never been initiated in the first place.
Correct. Most likely your actor was never initiated correctly in the first place. In your console, you should see more error messages than what you are seeing from the response of Play Webpage.
For example below is the error I actually got before I got the AskTimeoutException.
1 error
akka.actor.ActorInitializationException: akka://application/user/user-actor: exception during creation
at akka.actor.ActorInitializationException$.apply(Actor.scala:193)
at akka.actor.ActorCell.create(ActorCell.scala:608)
at akka.actor.ActorCell.invokeAll$1(ActorCell.scala:462)
at akka.actor.ActorCell.systemInvoke(ActorCell.scala:484)
at akka.dispatch.Mailbox.processAllSystemMessages(Mailbox.scala:282)
at akka.dispatch.Mailbox.run(Mailbox.scala:223)
at akka.dispatch.Mailbox.exec(Mailbox.scala:234)
at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
Caused by: com.google.inject.ConfigurationException: Guice configuration errors:
To wrap up, you may want to check the sbt console again and also your injection to make sure the actor was initiated in the first place.

Scala: unit-testing a method called asynchronously and returns a Future

I have a Scala method that reads as follows:
def doAnAsynchOperation(bunchedFilesUnit:BunchOfFilesUnit):
Future[FilesHandled] = {
val handleFuture = future {
BunchOfFilesUnit.unit.map(file => {
//FileParsing code - instantiate a class for file parsing
//Open a fileInputStream
//get an OutputStream
try {
//do some parsing, pass in a "file" invoke a parsing method
} catch {
case e:Throwable =>
//some code
} finally {
fileInputStream.close()
outStream.close()
}
})
}
I am calling this method in my Unit Test written in a "describe" and inside of it, I am going to have an "it".
There are 5 threads. Each thread is assigned the task of handling a single BunchedFileUnit .
These 5 threads are calling doAnAsynchOperation method asynchronously - each on their own - to process their own BunchedFileUnit and this method returns a Future[BunchedFileUnit].
What I want to accomplish is this:
I would like to write a test or tests to test this scenario. I would like to incorporate a timeout.
I am not very clear about the timeout concept here, but I assume I have to set a timeout and anticipate a TimeOutException. How can I lay out my test for such a scenario? This is what I want to accomplish.
Currently, I am laying out my test as follows:
import org.scalatest._
import org.scalatest.concurrent.ScalaFutures
import scala.concurrent.{Await, ExecutionContext, duration, Future}
import duration._
import akka.actor.{Actor, ActorRef, ActorSystem}
import org.scalatest.MustMatchers
import java.io.File
class FileHandlerTest extends FunSpec
with MustMatchers
with ScalaFutures
with BeforeAndAfter with SomeTraitThatINeeed {
describe("------------"){
}
}
I'll go out on a limb and guess you're looking for the "whenReady" helper. I see you've already imported ScalaFutures, so you'd do something like this inside your example:
val files = Seq(...)
whenReady(doAnAsynchOperation(files), timeout(Span(6, Seconds))) { result =>
result mustEqual "the correct value"
}
If you're trying to test the failure condition, you could try giving it a really short timeout, but I'm not sure what that would accomplish another than ensuring it doesn't complete too fast or take too long.

How to schedule an hourly job with Play Framework 2.1?

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

How to execute on start code in scala Play! framework application?

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.

Starting Akka actors in Play

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()
}