Ways for heartbeat message - scala

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.

Related

In Akka, how do I manage blocking method calls, like reading from StdIn?

I'm beginning to using Scala and the AKKA pattern, and i have wrote this code, but it doesn't work and i don't know why...
I have created a little project that read user input from the console.
when this user have wrote a 'keyword', the keyWord Actor (Child) will interpret it and will communicate with the console Actor (Grand parent).
the action Actor will be use to broadcast and do some more stuff.
When i enter the command 'rename' in the console Actor, i enter into the action Actor and after that in the keyWord Actor and enter in the Rename Method, but after that nothing, i didn't enter into the rename method on the console Actor.
Can you help me ?
If you saw any wrong pratice, please don't hesite to tell me how to resolve that :).
Thank you !
Main
import ConsoleActor._
import akka.actor.ActorSystem
object Main extends App {
val system = ActorSystem("My-Little-IRC")
val consoleActor = system.actorOf(ConsoleActor.props, "consoleActor")
consoleActor ! ReadConsoleInput
system.terminate()
}
consoleActor
import ActionActor.TreatInputUser
import akka.actor.{Actor, Props}
import scala.io.StdIn
object ConsoleActor {
case class ReadConsoleInput()
case class StopLoop()
case class Rename()
case class WhoIAm()
def props = Props[ConsoleActor]
}
class ConsoleActor() extends Actor {
val keyWordActor = context.actorOf(KeyWordActor.props(this.self), "keyWordActor")
val actionActor = context.actorOf(ActionActor.props(keyWordActor), "actionActor")
var currentUser: String = ""
var loop: Boolean = true;
import ConsoleActor._
def isValidString( str: String ): Boolean = {
var isValid: Boolean = false
if (str != null && !str.trim().isEmpty)
isValid = true
isValid
}
def initiatePresentation( ) = {
println("Hi ! Who are you ?")
currentUser = StdIn.readLine()
println(s"Nice to meet you ${currentUser}, I'm your console app !")
}
def receive = {
case ReadConsoleInput => {
initiatePresentation
var value = ""
while (loop) {
println("Yes ?")
value = StdIn.readLine()
if (isValidString(value)) {
actionActor ! TreatInputUser(value)
}
}
}
case StopLoop => {
println("stop Loooop !")
loop = false
}
case Rename => {
println(s"${currentUser} was a good name ! Which is your new name ?")
currentUser = StdIn.readLine()
println(s"Nice to meet you -again- ${currentUser}")
}
case WhoIAm =>{
println(s"I'm ${currentUser}")
}
}
}
actionActor
import ActionActor.TreatInputUser
import akka.actor.{Actor, ActorRef, Props}
import akka.util.Timeout
import scala.concurrent.duration._
import akka.pattern.ask
import scala.concurrent.Await
object ActionActor {
case class TreatInputUser(string: String)
def props(keyWordActor: ActorRef) = Props(new ActionActor(keyWordActor))
}
class ActionActor(keyWordActor: ActorRef) extends Actor {
import KeyWordActor._
def receive = {
case TreatInputUser(string) => {
implicit val timeout = Timeout(5 seconds)
var isKeyWord = keyWordActor ? IsKeyWord(string)
val isKeyWordResult = Await.result(isKeyWord, timeout.duration).asInstanceOf[ Boolean ]
println(isKeyWordResult)
if (isKeyWordResult) {
keyWordActor ! HandleKeyWord(string)
}
else {
println("bla bla bla")
}
}
}
}
keyWord actor
import ConsoleActor.{Rename, StopLoop, WhoIAm}
import akka.actor.{Actor, ActorRef, Props}
object KeyWordActor {
case class IsKeyWord(key : String)
case class HandleKeyWord(key : String)
def props(consoleActor: ActorRef) = Props(new KeyWordActor(consoleActor))
}
class KeyWordActor(consoleActor: ActorRef) extends Actor {
import KeyWordActor._
val KeysWord : Map[ String, ()=> Any] = Map("rename" -> renameFunction, "stop" -> stopFunction, "42" -> uselessfunction, "john doe ?" -> AmIJohnDoe)
def renameFunction() = {
println("here")
consoleActor ! Rename
}
def stopFunction() = {
consoleActor ! StopLoop
}
def uselessfunction() = {
println("useless")
}
def AmIJohnDoe() ={
consoleActor ! WhoIAm
}
def receive = {
case IsKeyWord(key) => {
sender ! KeysWord.contains(key.toLowerCase)
}
case HandleKeyWord(key) => {
if (KeysWord.contains(key.toLowerCase)) {
KeysWord(key.toLowerCase)()
}
}
}
}
You must not block in the receive method. The way you wrote it (with a while loop), the initial ReadConsoleInput message never finishes processing, and any subsequent messages (like StopLoop) will sit untouched in the Actor mailbox forever.
If you must selectively read from StdIn (as opposed to just continuously reading in e.g. your Main class) then one approach could be to change your ConsoleActor so that when it receives a ReadConsoleInput message, it should just try to do StdIn.readLine once, and forward the result to the ActionActor. Since the StdIn.readLine call itself is also blocking, you should do it asynchronously. The "pipe" pattern comes in handy here:
import akka.pattern.pipe
import scala.concurrent.Future
//...
def receive = {
case ReadConsoleInput =>
import context.dispatcher //provide a thread pool to do async work
Future(StdIn.readLine()) //read a line of input asynchronously
.filter(isValidString) //only continue if line is valid
.map(TreatInputUser) //wrap the (valid) String in a message
.pipeTo(actionActor) //forward it
case Rename => ...
}
This way, the ConsoleActor immediately becomes available again to process new messages, while your ActionActor will receive a TreatInputUser message whenever the user finishes typing a line in the console.
You can apply the same pattern inside your ActionActor, instead of relying on Await.
If you want to close the loop so you can continue sending messages, I'd use behaviour to ensure that two StdIn.readLine calls are not interfering.

Akka actor get remaining message list

I have an actor computing something intensive, and only the last result ideally should count.
I would like that if it receive multiple messages of the same type A(data), only the last one is handled and the previous ones are discarded.
How can I achieve that?
Custom mailbox
You can try implement some custom mailbox, containing 0 or 1 message:
import akka.actor.{ActorRef, ActorSystem}
import akka.dispatch._
import com.typesafe.config.Config
class SingleMessageQueue extends MessageQueue {
var message = Option.empty[Envelope]
def enqueue(receiver: ActorRef, handle: Envelope) = message = Some(handle)
def dequeue() = {
val handle = message.orNull
message = None
handle
}
def numberOfMessages = message.size
def hasMessages = message.nonEmpty
def cleanUp(owner: ActorRef, deadLetters: MessageQueue) = message.foreach(deadLetters.enqueue(owner, _))
}
final case class SingleMessageMailbox() extends MailboxType with ProducesMessageQueue[SingleMessageQueue] {
def this(settings: ActorSystem.Settings, config: Config) = this()
override def create(owner: Option[ActorRef], system: Option[ActorSystem]): MessageQueue = new SingleMessageQueue
}
and next enable it for your actor as descrived in the mailbox section of the docs
Split actors
You can introduce pair of actors.
Manager, receiving a job, resending it to Worker whenever it's not working right now
Worker doing actual work and notifing it's manager when it's done
example:
import akka.actor.{Actor, ActorRef, Props}
object Worker {
case class Job()
case object JobDone
}
import Worker.{Job, JobDone}
class Worker extends Actor {
override def receive = {
case Job() ⇒
// your long job
context.parent ! JobDone
}
}
class Manager extends Actor {
var nextJob = Option.empty[(Job, ActorRef)]
val worker = context.actorOf(Props[Worker])
def working: Receive = {
case job: Job ⇒ nextJob = Some((job, sender))
case JobDone ⇒
nextJob match {
case Some((job, snd)) ⇒ worker.tell(job, snd)
case None ⇒ context.become(free)
}
}
def free: Receive = {
case job: Job ⇒
worker.tell(job, sender)
context.become(working)
}
override def receive = free
}

Data push using akka scheduler

I am looking for some example on akka scheduler usage. I have one actor (lets call it - dataProducer) implementation for retrieving data from some database. I would like to write one scheduler actor which will pole the dataProducer actor in 5 seconds interval. Also how to handle the case if data retrieval takes more time than scheduler interval. Will scheduleOnce method in Scheduler actor handle this?
Here is my scheduler actor
import java.util.concurrent.{Executors, TimeUnit}
import akka.actor.{Actor, Cancellable, Props}
import scala.concurrent.ExecutionContext
class SchedulerActor(interval: Long) extends Actor with LogF{
implicit val ec = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(100))
private var scheduler: Cancellable = _
override def preStart(): Unit = {
import scala.concurrent.duration._
scheduler = context.system.scheduler.schedule(
initialDelay = 0 seconds,
interval = FiniteDuration(interval, TimeUnit.SECONDS),
receiver = self,
message = FetchData
)
}
override def postStop(): Unit = {
scheduler.cancel()
}
def receive = {
case FetchData =>
logger.debug( "Fetch Data" )
sender() ! "Data Fetched!!!" //here I'll call dataProducer API
true
case unknown =>
throw new RuntimeException( "ERROR: Received unknown message [" + unknown + "], can't handle it" )
}
}
object SchedulerActor {
def props(interval: Long): Props = Props(new SchedulerActor(interval))
}
sealed trait FetchDataMessage
case object FetchData extends FetchDataMessage
Scheduler's scheduleOnce helps to execute a piece of after some delay.
Have different states and switch between states to accept different kind of messages and act accordingly. But when timeout happens scheduleOnce will take you to timeoutState.
ScheduleOnce will help the actor know that timeout has happened.
how to handle the case if data retrieval takes more time than scheduler interval ?
If data fetch takes more than specified time actor state changes timeoutState and in the timeout state say what should be done to the actor. Either you can retry or try different source.
I would like to write one scheduler actor which will pole the dataProducer actor in 5 seconds interval
In the result state wait for scheduleOnce with 5 seconds delay to request the dataProducer and whole thing repeats again.
Check this code to understand how it can be done.
import akka.actor.{Actor, Cancellable}
import stackoverflow.DBUtils.Entity
import scala.concurrent.Future
import scala.concurrent.duration._
import akka.pattern.pipe
object DBPollActor {
case class Result(results: List[Entity])
case object Schedule
case object Timeup
case object FetchData
}
object DBUtils {
case class Entity(name: String)
def doDBOperation: Future[List[Entity]] = {
Future.successful(List(Entity(name = "foo")))
}
}
class DBPollActor(timeout: Int) extends Actor {
import DBPollActor._
implicit val ex = context.system.dispatcher
var schedulerOpt: Option[Cancellable] = None
#scala.throws[Exception](classOf[Exception])
override def preStart(): Unit = {
super.preStart()
self ! FetchData
schedulerOpt = Some(context.system.scheduler.scheduleOnce(timeout seconds) {
context become timeoutState
self ! Timeup
})
}
override def receive: Receive = {
case msg#FetchData =>
context become startState
self forward msg
}
def startState: Receive = {
case FetchData =>
schedulerOpt.map(_.cancel())
context become resultState
DBUtils.doDBOperation.map(Result) pipeTo self
schedulerOpt = Some(context.system.scheduler.scheduleOnce(timeout seconds) {
context become timeoutState
self ! Timeup
})
}
def timeoutState: Receive = {
case Timeup =>
schedulerOpt.map(_.cancel())
//Timeout happened do something or repeat
}
def resultState: Receive = {
case result#Result(list) =>
schedulerOpt.map(_.cancel())
//Result available consume the result and repeat or doSomething different
context become resultState
DBUtils.doDBOperation.map(Result) pipeTo self
schedulerOpt = Some(context.system.scheduler.scheduleOnce(timeout seconds) {
context become timeoutState
self ! Timeup
})
case ex: Exception =>
schedulerOpt.map(_.cancel())
//future failed exit or retry
}
}

Testing Akka Typed behavior

How would I test that a given behavior sends the messages I expect?
Say, three messages of some type, one after the other...
With regular actors (untyped) there was the TestProbe from regular Akka with methods like expectedMsg:
http://doc.akka.io/api/akka/current/index.html#akka.testkit.TestProbe
With akka-typed I'm scratching my head still. There is something called EffectfulActorContext, but I've no idea how to use that.
Example
Say I am writing a simple PingPong service, that given a number n replies with Pong(n) n-times. So:
-> Ping(2)
Pong(2)
Pong(2)
-> Ping(0)
# nothing
-> Ping(1)
Pong(1)
Here is how this behavior might look:
case class Ping(i: Int, replyTo: ActorRef[Pong])
case class Pong(i: Int)
val pingPong: Behavior[Ping] = {
Static {
case Ping(i, replyTo) => (0 until i.max(0)).map(_=> replyTo ! Pong(i))
}
}
My Hack
Now since I can't figure out how to make this work, the "hack" that I am doing right now is making the actor always reply with a list of responses. So the behavior is:
case class Ping(i: Int, replyTo: ActorRef[List[Pong]])
case class Pong(i: Int)
val pingPong: Behavior[Ping] = {
Static {
case Ping(i, replyTo) => replyTo ! (0 until i.max(0)).map(_=>Pong(i)).toList
}
}
Given this hacky change, the tester is easy to write:
package com.test
import akka.typed.AskPattern._
import akka.typed.ScalaDSL._
import akka.typed.{ActorRef, ActorSystem, Behavior, Props}
import akka.util.Timeout
import com.test.PingPong.{Ping, Pong}
import org.scalatest.{FlatSpec, Matchers}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
object PingPongTester {
/* Expect that the given messages arrived in order */
def expectMsgs(i: Int, msgs: List[Pong]) = {
implicit val timeout: Timeout = 5 seconds
val pingPongBe: ActorSystem[Ping] = ActorSystem("pingPongTester", Props(PingPong.pingPong))
val futures: Future[List[Pong]] = pingPongBe ? (Ping(i, _))
for {
pongs <- futures
done <- {
for ((actual, expected) <- pongs.zip(msgs)) {
assert(actual == expected, s"Expected $expected, but received $actual")
}
assert(pongs.size == msgs.size, s"Expected ${msgs.size} messages, but received ${pongs.size}")
pingPongBe.terminate
}
} Await.ready(pingPongBe.whenTerminated, 5 seconds)
}
}
object PingPong {
case class Ping(i: Int, replyTo: ActorRef[List[Pong]])
case class Pong(i: Int)
val pingPong: Behavior[Ping] = {
Static {
case Ping(i, replyTo) => replyTo ! (0 until i.max(0)).map(_=>Pong(i)).toList
}
}
}
class MainSpec extends FlatSpec with Matchers {
"PingPong" should "reply with empty when Pinged with zero" in {
PingPongTester.expectMsgs(0, List.empty)
}
it should "reply once when Pinged with one" in {
PingPongTester.expectMsgs(1, List(Pong(1)))
}
it should "reply with empty when Pinged with negative" in {
PingPongTester.expectMsgs(-1, List.empty)
}
it should "reply with as many pongs as Ping requested" in {
PingPongTester.expectMsgs(5, List(Pong(5), Pong(5), Pong(5), Pong(5), Pong(5)))
}
}
I'm using EffectfulActorContext for testing my Akka typed actors and here is an untested example based on your question.
Note: I'm also using the guardianactor provided in the Akka-typed test cases.
class Test extends TypedSpec{
val system = ActorSystem("actor-system", Props(guardian()))
val ctx: EffectfulActorContext[Ping] = new EffectfulActorContext[Ping]("ping", Ping.props(), system)
//This will send the command to Ping Actor
ctx.run(Ping)
//This should get you the inbox of the Pong created inside the Ping actor.
val pongInbox = ctx.getInbox("pong")
assert(pongInbox.hasMessages)
val pongMessages = pongInbox.receiveAll()
pongMessages.size should be(1) //1 or whatever number of messages you expect
}
Edit (Some more info): Cases where I need to add a replyTo ActorRef in my messages I do the following:
case class Pong(replyTo: ActorRef[Response])
val responseInbox: SyncInbox[Response] = Inbox.sync[Response]("responseInbox")
Pong(responseInbox.ref)
My initial approach to testing was to extend Behavior class
class TestQueueBehavior[Protocol] extends Behavior[Protocol] {
val messages: BlockingQueue[Protocol] = new LinkedBlockingQueue[Protocol]()
val behavior: Protocol => Unit = {
(p: Protocol) => messages.put(p)
}
def pollMessage(timeout: FiniteDuration = 3.seconds): Protocol = {
messages.poll(timeout.toMillis, TimeUnit.MILLISECONDS)
}
override def management(ctx: ActorContext[Protocol], msg: Signal): Behavior[Protocol] = msg match {
case _ ⇒ ScalaDSL.Unhandled
}
override def message(ctx: ActorContext[Protocol], msg: Protocol): Behavior[Protocol] = msg match {
case p =>
behavior(p)
Same
}
}
then I could call behavior.pollMessage(2.seconds) shouldBe somethingToCompareTo which was very similar to using TestProbe.
Although I think EffectfulActorContext is the right way to go, unfortunately couldn't figure out how to properly use it.

akka: pattern for combining messages from multiple children

Here's the pattern I have come across:
An actor A has multiple children C1, ..., Cn. On receiving a message, A sends it to each of its children, which each do some calculation on the message, and on completion send it back to A. A would then like to combine the results of all the children to pass onto another actor.
What would a solution for this problem look like? Or is this an anti-pattern? In which case how should this problem be approached?
Here is a trivial example which hopefully illustrates my current solution. My concerns are that is duplicates code (up to symmetry); does not extend very well to 'lots' of children; and makes it quite hard to see what's going on.
import akka.actor.{Props, Actor}
case class Tagged[T](value: T, id: Int)
class A extends Actor {
import C1._
import C2._
val c1 = context.actorOf(Props[C1], "C1")
val c2 = context.actorOf(Props[C2], "C2")
var uid = 0
var c1Results = Map[Int, Int]()
var c2Results = Map[Int, Int]()
def receive = {
case n: Int => {
c1 ! Tagged(n, uid)
c2 ! Tagged(n, uid)
uid += 1
}
case Tagged(C1Result(n), id) => c2Results get id match {
case None => c1Results += (id -> n)
case Some(m) => {
c2Results -= id
context.parent ! (n, m)
}
}
case Tagged(C2Result(n), id) => c1Results get id match {
case None => c2Results += (id -> n)
case Some(m) => {
c1Results -= id
context.parent ! (m, n)
}
}
}
}
class C1 extends Actor {
import C1._
def receive = {
case Tagged(n: Int, id) => Tagged(C1Result(n), id)
}
}
object C1 {
case class C1Result(n: Int)
}
class C2 extends Actor {
import C2._
def receive = {
case Tagged(n: Int, id) => Tagged(C2Result(n), id)
}
}
object C2 {
case class C2Result(n: Int)
}
If you think the code looks god-awful, take it easy on me, I've just started learning akka ;)
In the case of many - or a varying number of - child actors, the ask pattern suggested by Zim-Zam will quickly get out of hand.
The aggregator pattern is designed to help with this kind of situation. It provides an Aggregator trait that you can use in an actor to perform your aggregation logic.
A client actor wanting to perform an aggregation can start an Aggregator based actor instance and send it a message that will kick off the aggregation process.
A new aggregator should be created for each aggregation operation and terminate on sending back the result (when it has received all responses or on a timeout).
An example of this pattern to sum integer values held by the actors represented by the Child class is listed below. (Note that there is no need for them to all be children supervised by the same parent actor: the SummationAggregator just needs a collection of ActorRefs.)
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import akka.actor._
import akka.contrib.pattern.Aggregator
object Child {
def props(value: Int): Props = Props(new Child(value))
case object GetValue
case class GetValueResult(value: Int)
}
class Child(value: Int) extends Actor {
import Child._
def receive = { case GetValue => sender ! GetValueResult(value) }
}
object SummationAggregator {
def props = Props(new SummationAggregator)
case object TimedOut
case class StartAggregation(targets: Seq[ActorRef])
case object BadCommand
case class AggregationResult(sum: Int)
}
class SummationAggregator extends Actor with Aggregator {
import Child._
import SummationAggregator._
expectOnce {
case StartAggregation(targets) =>
// Could do what this handler does in line but handing off to a
// separate class encapsulates the state a little more cleanly
new Handler(targets, sender())
case _ =>
sender ! BadCommand
context stop self
}
class Handler(targets: Seq[ActorRef], originalSender: ActorRef) {
// Could just store a running total and keep track of the number of responses
// that we are awaiting...
var valueResults = Set.empty[GetValueResult]
context.system.scheduler.scheduleOnce(1.second, self, TimedOut)
expect {
case TimedOut =>
// It might make sense to respond with what we have so far if some responses are still awaited...
respondIfDone(respondAnyway = true)
}
if (targets.isEmpty)
respondIfDone()
else
targets.foreach { t =>
t ! GetValue
expectOnce {
case vr: GetValueResult =>
valueResults += vr
respondIfDone()
}
}
def respondIfDone(respondAnyway: Boolean = false) = {
if (respondAnyway || valueResults.size == targets.size) {
originalSender ! AggregationResult(valueResults.foldLeft(0) { case (acc, GetValueResult(v)) => acc + v })
context stop self
}
}
}
}
To use this SummationAggregator from your parent actor you could do:
context.actorOf(SummationAggregator.props) ! StartAggregation(children)
and then handle AggregationResult somewhere in the parent's receive.
You can use ? instead of ! on the child actors - this will cause the child actors to return a Future with their (eventual) results, i.e. everything is still non-blocking up until you Await the outcome of the Future. The parent actor can then compose these Futures and send it on to another actor - it will already know each Future's identity and so you won't need to worry about tagging each message so that you can put them back in order later. Here's a simple example where each child returns a random Double, and you want to divide the first child's return value by the second child's return value (i.e. order matters).
import scala.concurrent.duration._
import akka.actor.{Props, Actor}
import akka.pattern.{ask, pipe}
import akka.util.Timeout
class A extends Actor {
val c1 = context.actorOf(Props[C], "C1")
val c2 = context.actorOf(Props[C], "C2")
// The ask operation involves creating an internal actor for handling
// this reply, which needs to have a timeout after which it is
// destroyed in order not to leak resources; see more below.
implicit val timeout = Timeout(5 seconds)
def receive = {
case _ => {
val f1 = c1 ? "anything" // Future[Any]
val f2 = c2 ? "anything" // Future[Any]
val result: Future[Double] = for {
d1 <- f1.mapTo[Double]
d2 <- f2.mapTo[Double]
} yield d1 / d2
}
}
class C extends Actor {
def receive = {
case _ => // random Double
}
}