I am pretty new to akka actor system, and was wandering what are the best practices for executing common logic in an actor.
So here is an example:
I have the following actor:
class MyActor #Inject()(eventBus: EventBus) extends Actor{
eventBus.subscribe(context.self, Topics.TimeoffPolicy)
override def receive: Receive = {
case Foo(name:String) => {
//Need to execute foo related logic, and then Bar related logic
}
case Bar(name:String) => {
//Need to execute bar related logic
}
case a: Any => log.warning(f"unknown message actor ${a.toString}")
}
}
So one option is to extract common logic into a method and call it when handling Foo like this:
class MyActor #Inject()(eventBus: EventBus) extends Actor{
eventBus.subscribe(context.self, Topics.TimeoffPolicy)
override def receive: Receive = {
case Foo(name:String) => {
foo(name)
bar(name)
}
case Bar(name:String) => {
bar(name)
}
case a: Any => log.warning(f"unknown message actor ${a.toString}")
}
}
Other option is to send message to myself:
class MyActor #Inject()(eventBus: EventBus) extends Actor{
eventBus.subscribe(context.self, Topics.TimeoffPolicy)
override def receive: Receive = {
case Foo(name:String) => {
foo()
self ! Bar(name)
}
case Bar(name:String) => {
bar(name)
}
case a: Any => log.warning(f"unknown message actor ${a.toString}")
}
}
Here, it make sense to send a message, and keep the logic encapsulated in every message handling, but I guess it is less error prune to extract the common logic to a method, and invoke it. what is recommended?
Same goes in case of a common logic between actors, one option is to send message over the event bus to the other actor to invoke, so actor1 will execute foo, and the other one will execute bar.
The second option is to extract the same logic into another class, and inject that class to both actors, so no in order to execute foo and bar, there will be no communication between the actors.
What do you think?
Both approaches seem legit to me, I'd choose one of the two depending on whether I have calls to other actors inside the shared logic or not. Basically, synchronous logic can perfectly be reused by extracting a method, and asynchronous logic will require passing a message back to self.
Messages to self are also definitely preferred to be sent from Future callbacks (actually, it is the only right way to do in order not to mess with order of execution of the actor's tasks) and in scheduled activities.
Here's a snippet to illustrate synchronous approach:
class MyActor extends Actor with ActorLogging {
def receive = {
case Foo(foo) =>
doSomeFooSpecificWork()
logFooOrBar()
sender() ! "foo done"
case Bar =>
logFooOrBar()
sender() ! "bar done"
}
def logFooOrBar() = log.debug("A common message is handled")
}
And here's what I'd write for asynchronous one:
import akka.pattern.{ask, pipe}
class MyActor extends Actor {
val logger = context.actorOf(Props[DedicatedLogger])
def receive = {
case Foo(foo) =>
doSomeFooSpecificWork()
loggerActor ? LogFooOrBar(sender(), foo) pipeTo self
case Bar(bar) =>
loggerActor ? LogFooOrBar(sender(), bar) pipeTo self
case LoggedFoo(reportTo, foo) => reportTo ! "foo done"
case LoggedBar(reportTo, bar) => reportTo ! "bar done"
}
}
Related
I'm currently working on an application with a signup process. This signup process will, at some point, communicate with external systems in an asynchronous manner. To keep this question concise, I'm showing you two important actors I've written:
SignupActor.scala
class SignupActor extends PersistentFSM[SignupActor.State, Data, DomainEvt] {
private val apiActor = context.actorOf(ExternalAPIActor.props(new HttpClient))
// At a certain point, a CreateUser(data) message is sent to the apiActor
}
ExternalAPIActor.scala
class ExternalAPIActor(apiClient: HttpClient) extends Actor {
override def preRestart(reason: Throwable, message: Option[Any]) = {
message.foreach(context.system.scheduler.scheduleOnce(3 seconds, self, _))
super.preRestart(reason, message)
}
def receive: Receive = {
case CreateUser(data) =>
Await.result(
apiClient.post(data)
.map(_ => UserCreatedInAPI())
.pipeTo(context.parent),
Timeout(5 seconds).duration
)
}
}
This setup seems to work as expected. When there is an issue with the external API (such as a timeout or network problems), the Future returned by HttpClient::post fails and will result in an exception thanks to Await.result. This, in turn thanks to the SupervisorStrategy of the SignupActor parent actor, will restart the ExternalAPIActor where we can re-send the last message to itself with a small delay to avoid deadlock.
I see a couple of issues with this setup:
Within the receive method of ExternalAPIActor, blocking occurs. As far as I understand, blocking within Actors is considered an anti-pattern.
The delay used to re-send the message is static. If the API is unavailable for longer periods of time, we will keep on sending HTTP requests every 3 seconds. I'd like some kind of exponential backoff mechanism here instead.
To continue on with the latter, I've tried the following in the SignupActor:
SignupActor.scala
val supervisor = BackoffSupervisor.props(
Backoff.onFailure(
ExternalAPIActor.props(new HttpClient),
childName = "external-api",
minBackoff = 3 seconds,
maxBackoff = 30 seconds,
randomFactor = 0.2
)
)
private val apiActor = context.actorOf(supervisor)
Unfortunately, this doesn't seem to do anything at all -- the preRestart method of ExternalAPIActor isn't called at all. When replacing Backoff.onFailure with Backoff.onStop, the preRestart method is called, but without any kind of exponential backoff at all.
Given the above, my questions are as follows:
Is using Await.result the recommended (the only?) way to make sure exceptions thrown in a Future returned from services called within actors are caught and handled accordingly? An especially important part of my particular use case is the fact that messages shouldn't be dropped but retried when something went wrong. Or is there some other (idiomatic) way that exceptions thrown in asynchronous contexts should be handled within Actors?
How would one use the BackoffSupervisor as intended in this case? Again: it is very important that the message responsible for the exception is not dropped, but retried until a N-number of times (to be determined by the maxRetries argument of SupervisorStrategy.
Is using Await.result the recommended (the only?) way to make sure
exceptions thrown in a Future returned from services called within
actors are caught and handled accordingly?
No. Generally that's not how you want to handle failures in Akka. A better alternative is to pipe the failure to your own actor, avoiding the need to use Await.result at all:
def receive: Receive = {
case CreateUser(data) =>
apiClient.post(data)
.map(_ => UserCreatedInAPI())
.pipeTo(self)
case Success(res) => context.parent ! res
case Failure(e) => // Invoke retry here
}
This would mean no restart is required to handle failure, they are all part of the normal flow of your actor.
An additional way to handle this can be to create a "supervised future". Taken from this blog post:
object SupervisedPipe {
case class SupervisedFailure(ex: Throwable)
class SupervisedPipeableFuture[T](future: Future[T])(implicit executionContext: ExecutionContext) {
// implicit failure recipient goes to self when used inside an actor
def supervisedPipeTo(successRecipient: ActorRef)(implicit failureRecipient: ActorRef): Unit =
future.andThen {
case Success(result) => successRecipient ! result
case Failure(ex) => failureRecipient ! SupervisedFailure(ex)
}
}
implicit def supervisedPipeTo[T](future: Future[T])(implicit executionContext: ExecutionContext): SupervisedPipeableFuture[T] =
new SupervisedPipeableFuture[T](future)
/* `orElse` with the actor receive logic */
val handleSupervisedFailure: Receive = {
// just throw the exception and make the actor logic handle it
case SupervisedFailure(ex) => throw ex
}
def supervised(receive: Receive): Receive =
handleSupervisedFailure orElse receive
}
This way, you only pipe to self once you get a Failure, and otherwise send it to the actor the message was meant to be sent to, avoiding the need for the case Success I added to the receive method. All you need to do is replace supervisedPipeTo with the original framework provided pipeTo.
Alright, I've done some more thinking and tinkering and I've come up with the following.
ExternalAPIActor.scala
class ExternalAPIActor(apiClient: HttpClient) extends Actor with Stash {
import ExternalAPIActor._
def receive: Receive = {
case msg # CreateUser(data) =>
context.become(waitingForExternalServiceReceive(msg))
apiClient.post(data)
.map(_ => UserCreatedInAPI())
.pipeTo(self)
}
def waitingForExternalServiceReceive(event: InputEvent): Receive = LoggingReceive {
case Failure(_) =>
unstashAll()
context.unbecome()
context.system.scheduler.scheduleOnce(3 seconds, self, event)
case msg:OutputEvent =>
unstashAll()
context.unbecome()
context.parent ! msg
case _ => stash()
}
}
object ExternalAPIActor {
sealed trait InputEvent
sealed trait OutputEvent
final case class CreateUser(data: Map[String,Any]) extends InputEvent
final case class UserCreatedInAPI() extends OutputEvent
}
I've used this technique to prevent the original message from being lost in case there is something wrong with the external service we're calling. During the process of a request to an external service, I switch context, waiting for either a response of a failure and switch back afterwards. Thanks to the Stash trait, I can make sure other requests to external services aren't lost as well.
Since I have multiple actors in my application calling external services, I abstracted the waitingForExternalServiceReceive to its own trait:
WaitingForExternalService.scala
trait WaitingForExternalServiceReceive[-tInput, +tOutput] extends Stash {
def waitingForExternalServiceReceive(event: tInput)(implicit ec: ExecutionContext): Receive = LoggingReceive {
case akka.actor.Status.Failure(_) =>
unstashAll()
context.unbecome()
context.system.scheduler.scheduleOnce(3 seconds, self, event)
case msg:tOutput =>
unstashAll()
context.unbecome()
context.parent ! msg
case _ => stash()
}
}
Now, the ExternalAPIActor can extend this trait:
ExternalAPIActor.scala
class ExternalAPIActor(apiClient: HttpClient) extends Actor with WaitingForExternalServiceReceive[InputEvent,OutputEvent] {
import ExternalAPIActor._
def receive: Receive = {
case msg # CreateUser(data) =>
context.become(waitingForExternalServiceReceive(msg))
apiClient.post(data)
.map(_ => UserCreatedInAPI())
.pipeTo(self)
}
}
object ExternalAPIActor {
sealed trait InputEvent
sealed trait OutputEvent
final case class CreateUser(data: Map[String,Any]) extends InputEvent
final case class UserCreatedInAPI() extends OutputEvent
}
Now, the actor won't get restarted in case of failures/errors and the message isn't lost. What's more, the entire flow of the actor now is non-blocking.
This setup is (most probably) far from perfect, but it seems to work exactly as I need it to.
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
}
The context.parent reference does not do it, for me. In a Play project, I launch a new actor to deal with each incoming request:
val myActor: ActorRef = system.actorOf(Props(classOf[MyActor])
val future: Future[String] = (myActor ? Hello)(5.seconds).mapTo[String]
future.map(result => Ok(result)).recover {
case ex: AskTimeoutException => Ok("timeout expired")
case _ => Ok("error")
}
This triggers more actor messaging in the receive method of myActor: when anotherActor responds to myActor, the latter sends a message AllGood to its parent.
class MyActor extends Actor {
// other stuff, among which setup of anotherActor
def receive: Actor.Receive = {
case Hallo => anotherActor ! Hello
case HelloBack => context.parent ! AllGood
}
}
class AnotherActor extends Actor {
def receive: Actor.Receive = {
case Hallo => sender ! HelloBack
}
}
Everything works fine until myActor tries to send a message to context.parent. I get a deadLetters message in the console.
from Actor[xyz] to Actor[akka://my-system/user] was not delivered. [1] dead letters encountered.
I can make it work if myActor keeps a reference to the original sender, and the code looks like this instead:
class MyActor extends Actor {
var myDad: Option[ActorRef] = None // <--- NEW REFERENCE
// other stuff, among which setup of anotherActor
def receive: Actor.Receive = {
case Hallo => {
myDad = Some(sender)
anotherActor ! Hello
}
case HelloBack => myDad ! AllGood <-- MYDAD INSTEAD OF CONTEXT.PARENT
}
}
class AnotherActor extends Actor {
def receive: Actor.Receive = {
case Hallo => sender ! HelloBack
}
}
The reference myDad is actually something like Actor[akka://my-system/temp/$a], while I would expect it to be the same as the context.parent which was Actor[akka://my-system/user]. Isn't it the same actor?
Can anybody explain this behaviour to me, and suggest a cleaner way to fix this? Thanks.
Looks like you mixed up the concepts of parent and sender.
context.parent [akka://my-system/user] is normal because the actor has been created as a child of the Akka system using system.actorOf(Props(classOf[MyActor]).
the sender method gives you back the actor who sent the message. The important thing here is; this is not the actor system who sent the message to your actor. When using the ask pattern Akka creates a temporary actor (in your case Actor[akka://my-system/temp/$a]) that will send the message and wait for the answer. When the response is received, the Future is completed.
If you want it is possible to forward the original sender so that your AnotherActor can reply directly HelloBack to the temporary actor created by the ask pattern.
To do so just change the way the message is passed to anotherActor.
def receive: Actor.Receive = {
case Hallo => anotherActor.tell(Hello, sender)
}
I'm quite new to Akka so my question may seem simple:
I have an actor called workerA that uses FSM and can thus be either in those two states Finishedand Computing:
sealed trait State
case object Finished extends State
case object Computing extends State
sealed trait Data
case object Uninitialized extends Data
case class Todo(target: ActorRef, queue: immutable.Seq[Any]) extends Data
When workerA receives GetResponse it should answer if and if only it is in state Finished.
What is the proper way of doing this? I know we should avoid to be blocking in this paradigm but here it is only the top actor which is concerned.
Thanks
I'm not necessarily sure you even need FSM here. FSM is a really good tool for when you have many states and many possible (and possibly complicated) state transitions between those states. In your case, if I understand correctly, you basically have two states; gathering data and finished. It also seems that there is only a single state transition, going from gathering -> finished. If I have this all correct, then I'm going to suggest that you simply use become to solve your problem.
I have some code below to show a trivial example of what I'm describing. The basic idea is that the main actor farms some work off to some workers and then waits for the results. If anyone asks for the results while the work is being done, the actor stashes that request until the work is done. When done, the actor will reply back to anyone that has asked for the results. The code is as follows:
case object GetResults
case class Results(ints:List[Int])
case object DoWork
class MainActor extends Actor with Stash{
import context._
override def preStart = {
val a = actorOf(Props[WorkerA], "worker-a")
val b = actorOf(Props[WorkerB], "worker-b")
a ! DoWork
b ! DoWork
}
def receive = gathering(Nil, 2)
def gathering(ints:List[Int], count:Int):Receive = {
case GetResults => stash()
case Results(i) =>
val results = i ::: ints
val newCount = count - 1
if (newCount == 0){
unstashAll()
become(finished(results))
child("worker-a") foreach (stop(_))
child("worker-b") foreach (stop(_))
}
else
become(gathering(results, newCount))
}
def finished(results:List[Int]):Receive = {
case GetResults => sender ! results
}
}
class WorkerA extends Actor{
def receive = {
case DoWork =>
//Only sleeping to simulate work. Not a good idea in real code
Thread sleep 3000
val ints = for(i <- 2 until 100 by 2) yield i
sender ! Results(ints.toList)
}
}
class WorkerB extends Actor{
def receive = {
case DoWork =>
//Only sleeping to simulate work. Not a good idea in real code
Thread sleep 2000
val ints = for(i <- 1 until 100 by 2) yield i
sender ! Results(ints.toList)
}
}
Then you could test it as follows:
val mainActor = system.actorOf(Props[MainActor])
val fut = mainActor ? GetResults
fut onComplete (println(_))
You can pattern match on FSM states:
// insert pattern matching stuff instead of ...
class MyActor extends Actor with FSM[State, Message] {
startWith(Finished, WaitMessage(null))
when(Finished) {
case Event(Todo(... =>
// work
goto(Computing) using Todo(...)
case Event(GetResponse(... =>
// reply: sender ! msg // or similar
}
/* the rest is optional. You can use onTransition below to send yourself a message to report status of the job: */
when(Busy) {
case Event(Finished(... =>
// reply to someone: sender ! msg // or similar
goto(Finished)
}
onTransition {
case Finished -> Computing =>
// I prefer to run stuff here in a future, and then send a message to myself to signal the end of the job:
self ! Finished(data)
}
An Edit to more specifically address the question:
class MyActor extends Actor with FSM[State, Message] {
startWith(Finished, WaitMessage(null))
when(Finished) {
case Event(Todo(... =>
// work
goto(Computing) using Todo(...)
case Event(GetResponse(... =>
// reply: sender ! msg // or similar
stay
}
initialize()
}
How do I run 2 Akka actors with the caller sending the consumer a message every n seconds?
As there are no loop-react methods as in the Scala.Actors library I am stuck
somehow, the following will not compile and produces:
overriding method receive in trait Actor of type =>
Caller.this.Receive; method receive has incompatible type
object Foo {
def init() {
actorOf[Caller].start()
actorOf[Consumer].start()
}
}
class Caller extends Actor {
def receive {
while (true) {
self ! "msg"
Thread.sleep(1000)
}
}
}
class Consumer extends Actor {
def receive = {
case msg:String => doStuff()
case e => _
}
}
You're missing the equals sign after receive in Caller. Without it, the method is defined as returning Unit (i.e. no useful value), and akka needs you to return a PartialFunction[Any,Unit] from receive.
Now, to actually implement your logic in the idiomatic way, you probably want to use a ReceiveTimeout, like so:
class Caller extends Actor {
self.receiveTimeout = Some(1000)
def receive = {
case ReceiveTimeout =>
self ! "msg"
}
}