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.
Related
I am trying to get "ask" working for Akka Typed. I have followed examples online, and I thought I pretty much replicated what they showed, but I'm getting an compiler error when I try to evaluate the response from the "ask". Here's my minimal reproducible example.
SuperSimpleAsker is an actor that is requesting a "widget" from the MyWidgetKeeper actor. The response is a string representing the widget's id. All I'm trying to do so far is log the received widget id as a "Success" message, and will add more stuff to do with the id later. When the SuperSimpleAsker is created, the ActorRef of the MyWidgetKeeper is passed in. I have left out the Main program that creates the actors to keep the code simple.
The error that I get is:
type mismatch;
found : Unit
required: widgets.SuperSimpleAsker.Request
This error occurs on both of the logger.* lines (inside of the Case Failure and Case Success blocks toward the end of the code listing).
I don't understand what part of the code is requiring a "widgets.SuperSimpleAsker.Request" object or why.
package widgets
import scala.concurrent.duration.DurationInt
import scala.util.{Failure, Success}
import akka.actor.typed.scaladsl.Behaviors
import akka.actor.typed.{ActorRef, Behavior}
import akka.util.Timeout
import com.typesafe.scalalogging.LazyLogging
object MyWidgetKeeper {
sealed trait Request
case class GetWidget(replyTo: ActorRef[Response]) extends Request
sealed trait Response
case class WidgetResponse(widget: String) extends Response
def apply(): Behavior[Request] =
new MyWidgetKeeper().myWidgetKeeper()
}
class MyWidgetKeeper {
import MyWidgetKeeper._
def myWidgetKeeper(): Behavior[Request] = {
Behaviors.receive { (context, message) =>
message match {
case GetWidget(replyTo) =>
replyTo ! WidgetResponse("12345")
Behaviors.same
}
}
}
}
object SuperSimpleAsker {
sealed trait Request
case object DoStuff extends Request
def apply(widgetKeeper: ActorRef[MyWidgetKeeper.Request]): Behavior[Request] =
new SuperSimpleAsker(widgetKeeper).simpleAsker()
}
class SuperSimpleAsker(widgetKeeper: ActorRef[MyWidgetKeeper.Request]) extends LazyLogging{
import SuperSimpleAsker._
import widgets.MyWidgetKeeper.GetWidget
private def simpleAsker(): Behavior[Request] = {
Behaviors.receive { (context, message) =>
message match {
case DoStuff =>
logger.info(f"Doing stuff")
implicit val timeout = Timeout(2000 millis)
context.ask(widgetKeeper, GetWidget)
{
case Failure(exception) =>
logger.error(f"Failed: ${exception.getMessage}")
case Success(response: MyWidgetKeeper.Response) =>
response match {
case MyWidgetKeeper.WidgetResponse(id) =>
logger.info(f"Success: Got Widget# $id")
// Do some more stuff with the widget id
}
}
Behaviors.same
}
}
}
}
In Akka Typed's context.ask, the passed function converts the successful or failed ask into a message which gets sent to the actor, ideally without performing a side effect.
So your SuperSimpleAsker will have to add messages that the ask can be converted to:
object SuperSimpleAsker {
sealed trait Request
case object DoStuff extends Request
case class WidgetResponseFor(widgetId: String) extends Request
case object NoWidgetResponse extends Request
def apply(widgetKeeper: ActorRef[MyWidgetKeeper.Request]): Behavior[Request] =
new SuperSimpleAsker(widgetKeeper).simpleAsker()
}
class SuperSimpleAsker(widgetKeeper: ActorRef[MyWidgetKeeper.Request]) extends LazyLogging{
import SuperSimpleAsker._
import widgets.MyWidgetKeeper.GetWidget
private def simpleAsker(): Behavior[Request] = {
Behaviors.receive { (context, message) =>
message match {
case DoStuff =>
logger.info(f"Doing stuff")
implicit val timeout = Timeout(2000 millis)
context.ask(widgetKeeper, GetWidget)
{
case Failure(_) => // there's actually only one possible exception: timed out
NoWidgetResponse
case Success(response: MyWidgetKeeper.Response) =>
WidgetResponseFor(response.widget)
}
Behaviors.same
case WidgetResponseFor(id) =>
logger.info(f"Success: Got Widget# $id")
// Do stuff with the widget id
Behaviors.same
case NoWidgetResponse =>
logger.error("Failed")
Behaviors.same
}
}
}
}
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
}
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
}
}
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"
}
}
}
}
I'm trying to access the state of a particular actor through messaging. What I don't know is how I can retrieve the state of the actor. Here, I need to access the state variable of Node, state1. I want to avoid using promises/futures in the code. How should I go about modifying this code if I want to do that?
class Node extends Actor {
val state1:Int = 4
def receive = {
case getState => {
sender ! ??? //How should I send the 'state1' to the sender?
}
}
}
class Master extends Actor {
def recieve = {
case someCase(node_actor:ActorRef) => {
// My aim here is to get the "state1" from node actor into var 's'
var s:Int = node_actor ! getState
}
}
}
Actors are designed perfectly to avoid manual handling of scala.concurrent. things.
Just separate request and response handling into different receive cases:
class Node extends Actor {
import Node._
val state1: Int = 4
def receive: Receive = {
case getState =>
sender ! State(state1)
}
}
class Master extends Actor {
import Master._
def receive: Receive = {
case Action(node) =>
// My aim here is to get the "state1" from node actor into var 's'
node ! Node.GetState
case Node.State(state) =>
// do something with state
}
}
object Master {
case class Action(node: ActorRef)
}
object Node {
case object GetState
case class State(state: Int)
}
Sometimes you could have also some intermediate values calculated and don't want to do something until you'll get the answer but being unreachable also is not acceptable. So you could just wait for node response in separate Receive while stashing incoming messages, like this:
class Master extends Actor with Stash {
import Master._
def receive: Receive = {
case Action(node) =>
val intermediate = scala.util.Random.nextLong()
node ! Node.GetState
context.become(waitingForState(calc = intermediate), discardOld = false)
}
def waitingForState(calc: Long): Receive = {
case Node.State(state) =>
// do something with state and calc
context.unbecome()
unstashAll()
case _ => stash()
}
}