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()
}
}
Related
I followed the documentation at https://www.playframework.com/documentation/2.5.x/ScalaWebSockets
I wrote a chat room with the actor:
def socket = WebSocket.acceptOrResult[String, String] { request =>
ActorFlow.actorRef(out => MyWebSocketActor.props(out))
}
import akka.actor._
class MyWebSocketActor(out:ActorRef) extends Actor{
def receive={
case msg:String =>{
out ! ("I received your message:"+msg)
//println(msg)
}
}
}
object MyWebSocketActor{
def props(out:ActorRef)=Props(new MyWebSocketActor(out))
}
But I have got a problem here: While two devices enter my chat room, they have their words only.
How do I create a chat room with Akka Streams and Actor?
You need modeling your system with actors. MyWebSocketActor represents a single user. You need actor, that represents room with users.
I wrote simple example (explanation in comments):
object MyWebSocketActor {
case object Init
}
class MyWebSocketActor(out: ActorRef) extends Actor {
//you can pass precreated room in constructor or receive it from message,
//based on your logic
val room: ActorRef = ???
//when actor starts, it register self in room, we send Init
//message, because actors communications should be in `receive`
override def preStart(): Unit = {
self ! Init
}
//initial state, waiting joining in room
def waitingToJoin: Receive = {
case Init => room ! Join(self)
case Joined => context become joined
}
//joined state, process messages from out and room
def joined: Receive = {
case textFromOut: String => room ! Msg(textFromOut)
case msg: Msg => out ! msg.toString
}
//initial state
override def receive: Receive = waitingToJoin
}
object Room {
//request to join
case class Join(user: ActorRef)
//join response
case class Joined(room: ActorRef)
case class Msg(text: String)
}
class Room extends Actor {
//users in room
val users: ListBuffer[ActorRef] = ListBuffer()
override def receive: Receive = {
case msg: Msg =>
//send messages to all users
users.foreach(u => u ! msg)
//join request
case Join(user) =>
context watch user
users += user
user ! Joined(self)
case Terminated(user) =>
users -= user
}
}
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"
}
}
}
}
class A extends Actor{
def act = {
//do something
}
}
val a = new A
a.join // How Can I implement it
I have read How can I join started Actor?
however I still don't know how to write the code.
The standard way to achieve this would be to use the Ask Pattern
It goes something like this:
class MyActor extends Actor {
def receive = {
case "Ping" => sender ! "Pong"
}
}
val future = actor ? "Ping"
val result = Await.result(future, 10 seconds) //blocks until the response has been received, or the timeout reached
This is assuming that you want to block on a message from the actor. If you want to tell when an actor has died, you need to use DeathWatch like this:
case object TellMeWhenActorDies
case object ActorDied
class Watcher extends Actor {
val child = context.actorOf(Props[Watched], "watched")
context.watch(child)
override def receive: Receive = {
case TellMeWhenActorDies => context.become(waitingForDeath(sender))
}
def waitingForDeath(client: ActorRef): Receive = {
case Terminated(name) => client ! ActorDied
}
}
class Watched extends Actor {
override def receive: Receive = {
case _ => //do nothing
}
}
val finishedFuture = supervisor ? TellMeWhenActorDies
system.actorSelection("/user/$a/watched").tell(PoisonPill, supervisor)
Await.result(finishedFuture, 10 seconds)
Simply use the gracefulStop pattern. This is example is directly from the Akka docs:
try {
val stopped: Future[Boolean] = gracefulStop(actorRef, 5 seconds, Manager.Shutdown)
Await.result(stopped, 6 seconds)
// the actor has been stopped
} catch {
// the actor wasn't stopped within 5 seconds
case e: akka.pattern.AskTimeoutException =>
}
I have an actor which creates another one:
class MyActor1 extends Actor {
val a2 = system actorOf Props(new MyActor(123))
}
The second actor must initialize (bootstrap) itself once it created and only after that it must be able to do other job.
class MyActor2(a: Int) extends Actor {
//initialized (bootstrapped) itself, potentially a long operation
//how?
val initValue = // get from a server
//handle incoming messages
def receive = {
case "job1" => // do some job but after it's initialized (bootstrapped) itself
}
}
So the very first thing MyActor2 must do is do some job of initializing itself. It might take some time because it's request to a server. Only after it finishes successfully, it must become able to handle incoming messages through receive. Before that - it must not do that.
Of course, a request to a server must be asynchronous (preferably, using Future, not async, await or other high level stuff like AsyncHttpClient). I know how to use Future, it's not a problem, though.
How do I ensure that?
p.s. My guess is that it must send a message to itself first.
You could use become method to change actor's behavior after initialization:
class MyActor2(a: Int) extends Actor {
server ! GetInitializationData
def initialize(d: InitializationData) = ???
//handle incoming messages
val initialized: Receive = {
case "job1" => // do some job but after it's initialized (bootstrapped) itself
}
def receive = {
case d # InitializationData =>
initialize(d)
context become initialized
}
}
Note that such actor will drop all messages before initialization. You'll have to preserve these messages manually, for instance using Stash:
class MyActor2(a: Int) extends Actor with Stash {
...
def receive = {
case d # InitializationData =>
initialize(d)
unstashAll()
context become initialized
case _ => stash()
}
}
If you don't want to use var for initialization you could create initialized behavior using InitializationData like this:
class MyActor2(a: Int) extends Actor {
server ! GetInitializationData
//handle incoming messages
def initialized(intValue: Int, strValue: String): Receive = {
case "job1" => // use `intValue` and `strValue` here
}
def receive = {
case InitializationData(intValue, strValue) =>
context become initialized(intValue, strValue)
}
}
I don't know wether the proposed solution is a good idea. It seems awkward to me to send a Initialization message. Actors have a lifecycle and offer some hooks. When you have a look at the API, you will discover the prestart hook.
Therefore i propose the following:
When the actor is created, its preStart hook is run, where you do your server request which returns a future.
While the future is not completed all incoming messages are stashed.
When the future completes it uses context.become to use your real/normal receive method.
After the become you unstash everything.
Here is a rough sketch of the code (bad solution, see real solution below):
class MyActor2(a: Int) extends Actor with Stash{
def preStart = {
val future = // do your necessary server request (should return a future)
future onSuccess {
context.become(normalReceive)
unstash()
}
}
def receive = initialReceive
def initialReceive = {
case _ => stash()
}
def normalReceive = {
// your normal Receive Logic
}
}
UPDATE: Improved solution according to Senias feedback
class MyActor2(a: Int) extends Actor with Stash{
def preStart = {
val future = // do your necessary server request (should return a future)
future onSuccess {
self ! InitializationDone
}
}
def receive = initialReceive
def initialReceive = {
case InitializationDone =>
context.become(normalReceive)
unstash()
case _ => stash()
}
def normalReceive = {
// your normal Receive Logic
}
case class InitializationDone
}