Akka (Scala) - how to properly reset timer / scheduler? - scala

I have to fill in template to implement timeout on a shopping cart. So far I have:
import akka.actor.{Actor, ActorRef, Cancellable, Props, Timers}
import akka.event.{Logging, LoggingReceive}
import scala.concurrent.duration._
import scala.language.postfixOps
object CartActor {
sealed trait Command
case class AddItem(item: Any) extends Command
case class RemoveItem(item: Any) extends Command
case object ExpireCart extends Command
case object StartCheckout extends Command
case object ConfirmCheckoutCancelled extends Command
case object ConfirmCheckoutClosed extends Command
sealed trait Event
case class CheckoutStarted(checkoutRef: ActorRef) extends Event
def props = Props(new CartActor())
}
class CartActor extends Actor with Timers {
import context._
import CartActor._
private val log = Logging(context.system, this)
val cartTimerDuration: FiniteDuration = 5 seconds
var cart: Cart = Cart.empty
private def scheduleTimer: Cancellable =
system.scheduler.scheduleOnce(cartTimerDuration, self, ExpireCart)
def receive: Receive = empty
def empty: Receive = {
case AddItem(item) =>
this.cart = cart.addItem(item)
scheduleTimer
context become nonEmpty(cart, scheduleTimer)
case _ =>
}
def nonEmpty(cart: Cart, timer: Cancellable): Receive = {
case AddItem(item) =>
this.cart = cart.addItem(item)
timer.cancel()
scheduleTimer
case RemoveItem(item) =>
this.cart = this.cart.removeItem(item)
if (this.cart.size != 0) {
timer.cancel()
scheduleTimer
}
else
context become empty
case StartCheckout =>
context become inCheckout(this.cart)
case ExpireCart =>
this.cart = Cart.empty
println("Cart expired")
context become empty
}
def inCheckout(cart: Cart): Receive = {
case ConfirmCheckoutCancelled =>
context become nonEmpty(cart, scheduleTimer)
case ConfirmCheckoutClosed =>
println("Cart closed after checkout")
context become empty
case _ =>
}
}
Method signatures were provided, so e.g. I can't change def nonEmpty(cart: Cart, timer: Cancellable). While adding or removing item, the timer should be reset, so user has again 5 seconds to do something. The problem is I have no idea how to do it properly - the method above clearly does not reset the timer, as it always timeouts after 5 seconds.
How can I achieve that? Should I use timers instead of scheduler, e.g. timers.startSingleTimer("ExpireCart", ExpireCart, cartTimerDuration)? How should I pass this between methods? Should a timer be an attribute of the CartActor instead and I should ignore the scheduler? On a side note, when I have def nonEmpty(cart: Cart, timer: Cancellable), is the timer called anywhere implicitly, or just passed?

There are two problems.
Firstly, in empty you are starting two timers:
scheduleTimer // Creates a timer, reference lost so can't be cancelled
context become nonEmpty(cart, scheduleTimer) // Creates a second timer
More generally, you are using both mutable state and parameters to the receive method. The mutable state is not required, so delete this line:
var cart: Cart = Cart.empty
Now fix nonEmpty to pass the updated state to the new receive method rather than using this:
def nonEmpty(cart: Cart, timer: Cancellable): Receive = {
case AddItem(item) =>
timer.cancel()
context become nonEmpty(cart.addItem(item), scheduleTimer)
case RemoveItem(item) =>
timer.cancel()
if (this.cart.size > 1) {
context become nonEmpty(cart.remoteItem(item), scheduleTimer)
} else {
context become empty
}
case StartCheckout =>
timer.cancel()
context become inCheckout(cart)
case ExpireCart =>
timer.cancel() // Just in case :)
println("Cart expired")
context become empty
}

Related

How to implement receive function outside actor scope and pass it to actor

I have an actor, that can be in several states. Initial state should be passed from outside:
class MyActor(openId: String, initialState: Receive) extends Actor {
val connected: (String) => Receive = (openId: String) => {
case AuthorizedOk(user) => context.become(authorized(user))
...
case message => unhandled(message)
}
val authorized: (IUserInfo) => Receive = (user: IUserInfo) => {
case SomeLogic => context.become(...)
case message => unhandled(message)
}
def receive: Actor.Receive = initialState
}
I need to set initial state connected or authorized in constructor. Of course it may be some other function. But i don't understand how to achieve this:
new MyActor("id", ???)
I see two possibilities
Have the state passed in to the preStart lifecycle method of the actor
or
Have an object companion that can be used to pass the state when initialising the actor instance. Something in the lines of:
object MyActor {
def props(initialState: YourState) = Props.actorOf(new MyActor(initialState))
}
Also the initial state in your example need not be a partial function. You could model that as a case class and just use context.become to move between the states as they evolve.
EDIT: Adding an example to be a bit more clear!
sealed trait State
case object Authorized extends State
case object UnAuthorized extends State
class MyActor(state: State) extends Actor {
def receive: Receive = {
case state: State =>
// do some logic
val newState = someLogic(state)
// evolve with the new state
context.become(active(newState))
}
def active(newState: State): Receive = {
// do the pattern matching on the state and evolve
}
override def preStart(): Unit = {
super.preStart()
// send the initial state to this actor
self ! state
}
}

Is there a way to know the context of an Akka actor?

I have an actor with some contexts, i.e.
def step2: Receive = {
case _ => log error "Unhandled message"
}
def step1: Receive = {
case _ => log error "Unhandled message"
}
Is there a way to know on which state the actor currently is (step1, step2)?
(I could stock the value on a string but I wonder if there is a better way.)
If it not possible, I would like to understand why since this state should be kept somewhere.
FSM
You can use FSM. stateName gives the name of the state.
Ping the Actor with some special message handled in every state to send the stateName.
sealed trait ExampleState
case object State1 extends ExampleState
case object State2 extends ExampleState
case object C extends ExampleState
import akka.actor.{Actor, FSM}
import akka.event.EventHandler
import akka.util.duration._
case object Move
class ABC extends Actor with FSM[ExampleState, Unit] {
import FSM._
startWith(State1, Unit)
when(State1) {
case Ev(Move) =>
EventHandler.info(this, "Go to B and move on after 5 seconds")
goto(state2) forMax (5 seconds)
}
when(State2) {
case Ev(StateTimeout) =>
EventHandler.info(this, "Moving to C")
goto(C)
}
when(C) {
case Ev(Move) =>
EventHandler.info(this, "Stopping")
stop
}
initialize // this checks validity of the initial state and sets up timeout if needed
}
Hacky Solution
Akka Actor does not store any specific information about the PartialFunction. So I don't think there will be a akka lib function readily available for this.
Have state inside the actor and then update the state when Actor tries to become something.
class FooBar extends Actor with ActorLogging {
var state: Option[String] = Some("receive")
override def receive: Receive = {
case _ => context become state1()
}
def state1: () => Receive = {
() => {
state = Some("state1")
{
case _ => log error "ignore"
}
}
}
def state2: () => Receive = {
() => {
state = Some("state2")
{
case _ => log error "ignore"
}
}
}
}

Why is data always empty in this akka FSM actor?

Here is the code:
package com.packt.akka
import akka.actor.{ActorSystem, FSM, Props, Stash}
object UserStorageFSM {
// FSM State
sealed trait State
case object Connected extends State
case object Disconnected extends State
// FSM Data
sealed trait Data {
def data: List[User]
}
case object EmptyData extends Data {
val data = List.empty
}
trait DBOperation
object DBOperation {
case object Create extends DBOperation
case object Update extends DBOperation
case object Read extends DBOperation
case object Delete extends DBOperation
}
case object Connect
case object Disconnect
case class Operation(op: DBOperation, user: User)
case class User(username: String, email: String)
}
class UserStorageFSM extends FSM[UserStorageFSM.State, UserStorageFSM.Data] with Stash {
import UserStorageFSM._
// 1. define start with
startWith(Disconnected, EmptyData)
// 2. define states
when(Disconnected) {
case Event(Connect, _) =>
println("UserStorage Connected to DB")
unstashAll()
goto(Connected) using (EmptyData)
case Event(_, _) =>
stash()
stay using (EmptyData)
}
when(Connected) {
case Event(Disconnect, _) =>
println("UserStorage disconnected from DB")
goto(Disconnected) using EmptyData
case Event(Operation(op, user), oldData) =>
op match {
case DBOperation.Create =>
stay using new Data {
override def data = user :: oldData.data
}
case DBOperation.Delete => stay using new Data {
override def data = oldData.data.filter(_ != user)
}
case DBOperation.Read => {
println(oldData.data)
stay using oldData
}
case DBOperation.Update => {
stay using new Data {
override def data: List[User] = user :: oldData.data.filter(_.username != user.username)
}
}
}
stay using EmptyData
}
// 3. initialize
initialize()
}
object FiniteStateMachine extends App {
import UserStorageFSM._
val system = ActorSystem("Hotswap-FSM")
val userStorage = system.actorOf(Props[UserStorageFSM], "userStorage-fsm")
userStorage ! Connect
userStorage ! Operation(DBOperation.Create, User("Admin", "admin#packt.com"))
userStorage ! Operation(DBOperation.Create, User("Admin1", "admin#packt.com"))
userStorage ! Operation(DBOperation.Read, User("Admin", "admin#packt.com"))
userStorage ! Disconnect
Thread.sleep(1000)
system.terminate()
}
It simulates a storage system that allows CRUD operations. The problem here is that the system seems to always contain empty data. What went wrong here?
In your handler for Operation you call stay in the pattern match on op, but then at the bottom you call stay using EmptyData and that is the one that gets used. Remove stay using EmptyData from the bottom of case Event(Operation(op, user), oldData) => and you should start seeing the updated Data.

Ways for heartbeat message

I am trying to set a heartbeat over a network, i.e. having an actor send a message to the network on a fixed period of time. I would like to know if you have any better solution than the one I used below as I feel is pretty ugly, considering synchronisation contraints.
import akka.actor._
import akka.actor.Actor
import akka.actor.Props
import akka.actor.ScalaActorRef
import akka.pattern.gracefulStop
import akka.util._
import java.util.Calendar
import java.util.concurrent._
import java.text.SimpleDateFormat
import scala.Array._
import scala.concurrent._
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
sealed trait Message
case class Information() extends Message//does really need to be here?
case class StartMessage() extends Message
case class HeartbeatMessage() extends Message
case class StopMessage() extends Message
case class FrequencyChangeMessage(
f: Int
) extends Message
class Gps extends Actor {
override def preStart() {
val child = context.actorOf(Props(new Cadencer(500)), name = "cadencer")
}
def receive = {
case "beat" =>
//TODO
case _ =>
println("gps: wut?")
}
}
class Cadencer(p3riod: Int) extends Actor {
var period: Int = _
var stop: Boolean = _
override def preStart() {
period = p3riod
stop = false
context.system.scheduler.scheduleOnce(period milliseconds, self, HeartbeatMessage)
}
def receive = {
case StartMessage =>
stop = false
context.system.scheduler.scheduleOnce(period milliseconds, self, HeartbeatMessage)
case HeartbeatMessage =>
if (false == stop) {
context.system.scheduler.scheduleOnce(0 milliseconds, context.parent, "beat")
context.system.scheduler.scheduleOnce(period milliseconds, self, HeartbeatMessage)
}
case StopMessage =>
stop = true
case FrequencyChangeMessage(f) =>
period = f
case _ =>
println("wut?\n")
//throw exception
}
}
object main extends App {
val system = akka.actor.ActorSystem("mySystem")
val gps = system.actorOf(Props[Gps], name = "gps")
}
What I called cadencer here sends to a target actor and to itself an HeartbeatMessage ; to itself to transmit the order to resend one after a given amount of time, and thus going on with the process till a StopMessage (flipping the stop to true). Good?
Is even having a separated actor efficient rather than having it within a greater one?
Try this. It does not need a separate cadencer class.
class Gps extends Actor
{
var ticker : Cancellable = _
override def preStart()
{
println ("Gps prestart")
// val child = context.actorOf(Props(new Cadencer(500)), name = "cadencer")
ticker = context.system.scheduler.schedule (
500 milliseconds,
500 milliseconds,
context.self,
"beat")
}
def receive: PartialFunction[Any, Unit] =
{
case "beat" =>
println ("got a beat")
case "stop" =>
ticker.cancel()
case _ =>
println("gps: wut?")
}
}
object main extends App
{
val system = akka.actor.ActorSystem("mySystem")
val gps = system.actorOf(Props[Gps], name = "gps")
Thread.sleep (5000)
gps ! "stop"
println ("stop")
}
Actors are pretty lightweight, so it is no problem to have one actor for sending heartbeat messages (and it's preferable if you think of the Single Responsibility Principle).
Further remarks:
If you want to get rid of the period var, you can do the following (it's called hotswapping):
override def preStart() {
// ...
context.become(receive(p3riod))
}
def receive(period: Int) = {
// ...
case FrequencyChangeMessage(f) =>
context.become(receive(f))
// ...
}
Instead of using the stop var, you can stop the actor after getting the StopMessage.
If you need a heartbeat actor again, just start a new one.
Instead of scheduling with 0 milliseconds, you can send the message directly to the parent.

Akka, futures and critical sections

Let's say we have an Akka actor, which maintains an internal state in terms of a var.
class FooActor extends Actor {
private var state: Int = 0
def receive = { ... }
}
Let's say the reception handler invokes an operation that returns a future, we map it using the dispatcher as context executor and finally we set a onSuccess callback that alters the actor state.
import context.dispatcher
def receive = {
case "Hello" => requestSomething() // asume Future[String]
.map(_.size)
.onSuccess { case i => state = i }
}
Is it thread-safe to alter the state of the actor from the onSuccess callback, even using the actor dispatcher as execution context?
No it's not (akka 2.3.4 documentation).
What you have to do in this case is send a message to self to alter the state. If you need ordering you can use stash and become. Something like this
import akka.actor.{Stash,Actor}
import akka.pattern.pipe
case class StateUpdate(i:int)
class FooActor extends Actor with Stash{
private var state: Int = 0
def receive = ready
def ready = {
case "Hello" => requestSomething() // asume Future[String]
.map(StateUpdate(_.size)) pipeTo self
become(busy)
}
def busy {
case StateUpdate(i) =>
state=i
unstashAll()
become(ready)
case State.Failure(t:Throwable) => // the future failed
case evt =>
stash()
}
}
Of course this is a simplistic implementation you will probably want to handle timeout and stuff to avoid having your actor stuck.
if you don't need ordering guarantees on your state :
case class StateUpdate(i:int)
class FooActor extends Actor with Stash{
private var state: Int = 0
def receive = {
case "Hello" => requestSomething() // asume Future[String]
.map(StateUpdate(_.size)) pipeTo self
case StateUpdate(i) => state=i
}
but then the actor state may not be the length of the last string received
Just to support Jean's answer here's the example from the docs :
class MyActor extends Actor {
var state = ...
def receive = {
case _ =>
//Wrongs
// Very bad, shared mutable state,
// will break your application in weird ways
Future {state = NewState}
anotherActor ? message onSuccess {
r => state = r
}
// Very bad, "sender" changes for every message,
// shared mutable state bug
Future {expensiveCalculation(sender())}
//Rights
// Completely safe, "self" is OK to close over
// and it's an ActorRef, which is thread-safe
Future {expensiveCalculation()} onComplete {
f => self ! f.value.get
}
// Completely safe, we close over a fixed value
// and it's an ActorRef, which is thread-safe
val currentSender = sender()
Future {expensiveCalculation(currentSender)}
}
}