I'm new to Actors and I'm trying to understand how to chain actors properly. I've read Ask: Send-And-Receive-Future section of documentation.
Here is how I did it now
case class Message1(text : String)
class RootActor extends Actor {
def receive: Actor.Receive = {
case Message1(message) => {
(context.actorOf(Props( new TestActor1)) ? message)(5.seconds) pipeTo sender
}
}
}
class TestActor1 extends Actor {
def receive: Actor.Receive = {
case message : String => {
sender ! (message + " TestActor1")
}
}
}
object Test {
def main(args: Array[String]) {
println("Start!")
implicit val system = ActorSystem()
val rootActor = system.actorOf(Props( new RootActor))
for (reply1 <- (rootActor ? Message1("Begin"))(5.seconds).mapTo[String])
println(reply1)
}
}
So I manually relay reply. But shouldn't replies from child actor bubble up to its parent automatically ? Is this right way of doing it ?
Also I'm concerned that in manual way of bubbling up reply I use ask which requires timeout. But what if some child actor requires more time to do its job than others but I'm limited by one timeout value when I send message to root actor ? It seems to me that maintaining proper timeouts across entire chain is very cumbersome.
How can I simplify it ?
Just use forward instead. That's what it's there for. Use it like so :
context.actorOf(Props( new TestActor1)).forward(message)
Related
I am trying to create a web socket server using Play Framework where response from server should be synchronous or asynchronous based on request.
The request will be processed in Parent actor .Based on the action in the request, child actor will be created and message will be passed to child actor for processing and response will be sent back to the controller.
There are predefined actions and sample request for some actions are as follows,
[,,]
["1234","Boot","{"system":"ABCD"}"]
["5678","Start","{"system":"EFGH", "currenTime":"1559548762638"}"]
#Singleton
class RequestController #Inject()(cc: ControllerComponents)(implicit system: ActorSystem, mat: Materializer) extends AbstractController(cc) {
def ws = WebSocket.accept[String, String] {req =>
ActorFlow.actorRef { out =>
ParentActor.props(out)
}
}
}
object ParentActor {
def props(out: ActorRef) = Props(new ParentActor(out))
}
class ParentActor(out : ActorRef) extends Actor {
override def receive: Receive = {
case msg: String =>
//String split opeartion to find the action.
//create child actor for the action and pass the message to the child actor
val action = msg.split(",")[2]
if("Boot".equals(action)){
val bootActor: ActorRef = actorSystem.actorOf(Props[BootActor])
childActor ! msg
}else if("Start".equals(action)){
val startActor: ActorRef = actorSystem.actorOf(Props[StartActor])
startActor ! msg
}
case msg: Response => out ! msg
}
}
case class Response(name:String, msg:String)
class BootActor extends Actor{
override def receive: Receive = {
case msg : String =>
sender() ! Response("ABC",msg)
}
}
class StartActor extends Actor{
override def receive: Receive = {
case msg : String =>
sender() ! Response("Efgh",msg)
}
}
Right now i am getting the action from the request and create a child actor for the action and pass the message to the child actor for processing.
But i am not sure is there any better way or design pattern to process the request and create a child actor instead of String operation?
First of all, there appears to be a typo in your code:
if ("Boot".equals(action)) {
val bootActor: ActorRef = actorSystem.actorOf(Props[BootActor])
childActor ! msg
} else if ("Start".equals(action)) {
val startActor: ActorRef = actorSystem.actorOf(Props[StartActor])
startActor ! msg
}
The message in the first conditional clause should be sent to bootActor instead of childActor, which is undefined in your code snippet.
Another issue is that you're using actorSystem.actorOf to create the child actors. This method creates "top-level" actors, which should be kept to a minimum. Actors created with actorSystem.actorOf are under the supervision of the guardian actor. What this means in relation to your code is that when ParentActor is stopped (i.e., when a WebSocket is closed, Play stops the actor used in ActorFlow, as documented here), the multiple instances of BootActor and StartActor will not be stopped, leaving you with a bunch of idle top-level actors. The remedy is to use context.actorOf to create instances of BootActor and StartActor: doing so makes these instances children of ParentActor.
Also, you should use the == operator instead of the equals method.
Here are the aforementioned changes:
if ("Boot" == action) {
val bootActor: ActorRef = context.actorOf(Props[BootActor])
bootActor ! msg
} else if ("Start" == action) {
val startActor: ActorRef = context.actorOf(Props[StartActor])
startActor ! msg
}
The above could be slightly simplified to the following:
val childActor =
if (action == "Boot") context.actorOf(Props[BootActor])
else context.actorOf(Props[StartActor])
childActor ! msg
To further simplify your code, don't create child actors, which in this case aren't necessary. Move all the logic of interacting with the out actor into a single actor.
I have supervisor actor which selects child actor based on command received, whenever it creates a new child actor and sends message(ask pattern) it timesout as child actor tries to send back response but it goes to dead letters.
Here is the child actor code
class IdActor(id: String, injector: Injector) extends Actor {
override def receive: Receive = {
case cmd: GenerateIdCmd =>
val parent = sender()
...
Future(idEvent) pipeTo sender //Issue is here going to dead letters
//Future(idEvent) pipeTo parent //This also leads to same problem
//parent ! idEvent // Same issue
}
}
Here is supervisor code
class IdSupervisor(injector: Injector) extends Actor {
override def receive: Receive = {
case cmd: GenerateIdCmd =>
...
val ref = context.child(cmd.id).getOrElse {
context.actorOf(Props(classOf[IdActor], cmd.id, injector), cmd.id)
}
ask(ref, cmd) pipeTo sender
}
}
Second message is flowing back to originator, first response from all new child actors going to dead letters from there afterwards going good.
Working Solution
Issue is with Supervisor, fixed code
class IdSupervisor(injector: Injector) extends Actor {
override def receive: Receive = {
case cmd: GenerateIdCmd =>
val originator = sender
...
val ref = context.child(cmd.id).getOrElse {
context.actorOf(Props(classOf[IdActor], cmd.id, injector), cmd.id)
}
ask(ref, cmd) pipeTo originator
}
}
I suspect that the issue doesn't actually occur when a child replies to the parent as you describe, but when the parent sends a message to a child and expects a reply:
val ref = context.child(cmd.id).getOrElse {
context.actorOf(Props(classOf[IdActor], cmd.id, injector), cmd.id)
}
ask(ref, cmd) pipeTo sender
An actor is started asynchronously upon creation. The observation that, when a new child is created, the first message to that child results in dead letters, while subsequent messages to that child result in the intended behavior, suggests an actor initialization issue. What's probably happening is that the child actors receive their first GenerateIdCmd message before they have completely started.
Never ever pipe directly to sender from future inside actor, sender is def and at the moment when future is completed, it might be already different one than you expect. One of solution is to store sender before future call:
class IdActor(id: String, injector: Injector) extends Actor {
override def receive: Receive = {
case cmd: GenerateIdCmd =>
...
val originalSender = sender
Future(idEvent) pipeTo originalSender
}
}
I have two actors in my system. Talker and Conversation. Conversation consists in two talkers (by now). When a Talker wants to join a conversation I should check if conversation exists (another talker has created it) and if it not, create it. I have this code in a method of my Talker actor:
def getOrCreateConversation(conversationId: UUID): ActorRef = {
// #TODO try to get conversation actor by conversationId
context.actorSelection("user/conversation/" + conversationId.toString)
// #TODO if it not exists... create it
context.actorOf(Conversation.props(conversationId), conversationId.toString)
}
As you can see, when I create my converastion actor with actorOf I'm passing as a second argument the conversationId. I do this for easy searching this actor... Is it the correct way to do this?
Thank you
edited
Thanks to #Arne I've finally did this:
class ConversationRouter extends Actor with ActorLogging {
def receive = {
case ConversationEnv(conversationId, msg) =>
val conversation = findConversation(conversationId) match {
case None => createNewConversation(conversationId)
case Some(x) => x
}
conversation forward msg
}
def findConversation(conversationId: UUID): Option[ActorRef] = context.child(conversationId.toString)
def createNewConversation(conversationId: UUID): ActorRef = {
context.actorOf(Conversation.props(conversationId), conversationId.toString)
}
}
And the test:
class ConversationRouterSpec extends ChatUnitTestCase("ConversationRouterSpec") {
trait ConversationRouterSpecHelper {
val conversationId = UUID.randomUUID()
var newConversationCreated = false
def conversationRouterWithConversation(existingConversation: Option[ActorRef]) = {
val conversationRouterRef = TestActorRef(new ConversationRouter {
override def findConversation(conversationId: UUID) = existingConversation
override def createNewConversation(conversationId: UUID) = {
newConversationCreated = true
TestProbe().ref
}
})
conversationRouterRef
}
}
"ConversationRouter" should {
"create a new conversation when a talker join it" in new ConversationRouterSpecHelper {
val nonExistingConversationOption = None
val conversationRouterRef = conversationRouterWithConversation(nonExistingConversationOption)
conversationRouterRef ! ConversationEnv(conversationId, Join(conversationId))
newConversationCreated should be(right = true)
}
"not create a new conversation if it already exists" in new ConversationRouterSpecHelper {
val existingConversation = Option(TestProbe().ref)
val conversationRouterRef = conversationRouterWithConversation(existingConversation)
conversationRouterRef ! ConversationEnv(conversationId, Join(conversationId))
newConversationCreated should be(right = false)
}
}
}
Determining the existence of an actor cannot be done synchronously. So you have a couple of choices. The first two are more conceptual in nature to illustrate doing asynchronous lookups, but I offer them more for reference about the asynchronous nature of actors. The third is likely the correct way of doing things:
1. Make the function return a Future[ActorRef]
def getOrCreateConversation(conversationId: UUID): Unit {
context.actorSelection(s"user/conversation/$conversationId")
.resolveOne()
.recover { case _:Exception =>
context.actorOf(Conversation.props(conversationId),conversationId.toString)
}
}
2. Make it Unit and have it send the ActorRef back to your current actor
Pretty much the same as the above, but now you we pipe the future back the current actor, so that the resolved actor can be dealt with in the context of the calling actor's receive loop:
def getOrCreateConversation(conversationId: UUID): Unit {
context.actorSelection(s"user/conversation/$conversationId")
.resolveOne()
.recover { case _:Exception =>
context.actorOf(Conversation.props(conversationId),conversationId.toString)
}.pipeTo(self)
}
3. Create a router actor that you send your Id'ed messages to and it creates/resolves the child and forwards the message
I say that this is likely the correct way, since your goal seems to be cheap lookup at a specific named path. The example you give makes the assumption that the function is always called from within the actor at path /user/conversation otherwise the context.actorOf would not create the child at /user/conversation/{id}/.
Which is to say that you have a router pattern on your hands and the child you create is already known to the router in its child collection. This pattern assumes you have an envelope around any conversation message, something like this:
case class ConversationEnv(id: UUID, msg: Any)
Now all conversation messages get sent to the router instead of to the conversation child directly. The router can now look up the child in its child collection:
def receive = {
case ConversationEnv(id,msg) =>
val conversation = context.child(id.toString) match {
case None => context.actorOf(Conversation.props(id),id.toString)
case Some(x) => x
}
conversation forward msg
}
The additional benefit is that your router is also the conversation supervisor, so if the conversation child dies, it can deal with it. Not exposing the child ActorRef to the outside world also has the benefit that you could have it die when idle and have it get re-created on the next message receipt, etc.
I'm newbie in Akka and Scala and i come from a non-concurrent world. Probably i'm doing a lot of things wrong, i will appreciate feedback even it's not related to the question.
I'm doing a simple chat application with Akka and Scala. I started (bc business requirements) by "typing feature"... it's the typical feature in whatsapp or tellegram "John is typing a message".
I have modelled it using two actors types: Talkers and Conversation, and I want to unit test my Conversation actor. My Conversation actor looks like this:
object Conversation {
def props(conversationId: UUID, talkers: List[ActorRef])(out: ActorRef) = Props(new Conversation(conversationId, talkers))
case class Typing(talkerId: TalkerId)
}
class Conversation(conversationId: UUID, talkers: List[ActorRef]) extends Actor with ActorLogging {
def receive = LoggingReceive {
case Typing(talkerId) =>
// notify all talkers that a talker is typing
// #TODO don't notify user which is typing
talkers foreach {talker: ActorRef => talker ! InterlocutorTyping(talkerId)}
}
}
I think, by now is very simple. So, before start coding in Scala and Akka I had tested this like:
I get my Conversation actor
I mock talkers
I send a message Typing to my actor
I expect that talkers should be notified
I don't really know if it's the correct approach in Scala and Akka. My test (using scalatest) looks like this:
"Conversation" should {
"Notify interlocutors when a talker is typing" in {
val talkerRef1 = system.actorOf(Props())
val talkerRef2 = system.actorOf(Props())
val talkerRef1Id = TalkerIdStub.random
val conversationId = UUID.randomUUID()
val conversationRef = system.actorOf(Props(classOf[Conversation], conversationId, List(talkerRef1, talkerRef2)))
// should I use TestActorRef ?
conversationRef ! InterlocutorTyping(talkerRef1Id)
// assert that talker2 is notified when talker1 is typing
}
}
Should I use TestActorRef? Should I use TestProbe() (I read that this is for integration tests)
How can I create Talker mocks? Is this approach correct?
It's correct to inject a List of Talkers to my conversation Actor?
I searched for documentation, but I think there are a lot too old and I'm not sure if the code examples are still functional.
Thank you for your time guys, and sorry about this noob question :=)
It's true that the testing situation in Akka is a little confusing to say the least.
In Akka generally you have two kinds of test, synchronous and asynchronous which some people term as 'unit' and 'integration' tests.
'Unit tests' are synchronous, you directly test the receive method without requiring an actor system, etc. In your case, you would want to mock the List[Talkers], call your receive method and verify that the send method is called. You can directly instantiate your actor with new Conversation(mockTalkers), it's not necessary in this case to use TestActorRef. For mocking, I recommend ScalaMock.
'Integration tests' are asynchronous, and generally test more than one actor working together. This is where you inherit TestKit, instantiate TestProbes to act as your talkers, use one to send a message to the Conversation actor, and verify that the other receives the InterlocutorTyping message.
It's up to you which kind of test you think is appropriate. My personal opinion is that unless you have complicated internal bevaviour in your actor, you should skip the synchronous tests and go straight for the asynchronous ('integration') tests as this will cover more tricky concurrency edge cases that you might otherwise miss. These are also more 'black-box' and so less sensitive to change as you evolve your design.
More details and code samples on the doc page.
Finally I did this (there are some more functionality than in the question):
object Conversation {
def props(conversationId: UUID)(out: ActorRef) = Props(new Conversation(conversationId))
case class TalkerTyping(talkerId: TalkerId)
case class TalkerStopTyping(talkerId: TalkerId)
case class Join(talker: ActorRef)
case class Leave(talker: ActorRef)
}
class Conversation(conversationId: UUID) extends Actor with ActorLogging {
var talkers : ListBuffer[ActorRef] = ListBuffer.empty
val senderFilter = { talker: ActorRef => talker != sender() }
def receive = LoggingReceive {
case Join =>
talkers += sender()
case Leave =>
talkers -= sender()
case TalkerTyping(talkerId) => // notify all talkers except sender that a talker is typing
talkers filter senderFilter foreach { talker: ActorRef => talker ! InterlocutorTyping(talkerId) }
case TalkerStopTyping(talkerId) => // notify all talkers except sender that a talker has stopped typing
talkers filter senderFilter foreach { talker: ActorRef => talker ! InterlocutorStopTyping(talkerId) }
}
}
And my test:
class ConversationSpec extends ChatUnitTestCase("ConversationSpec") {
trait ConversationTestHelper {
val talker = TestProbe()
val anotherTalker = TestProbe()
val conversationRef = TestActorRef[Conversation](Props(new Conversation(UUID.randomUUID())))
val conversationActor = conversationRef.underlyingActor
}
"Conversation" should {
"let user join it" in new ConversationTestHelper {
conversationActor.talkers should have size 0
conversationRef ! Join
conversationActor.talkers should have size 1
conversationActor.talkers should contain(testActor)
}
"let joining user leave it" in new ConversationTestHelper {
conversationActor.talkers should have size 0
conversationRef ! Join
conversationActor.talkers should have size 1
conversationActor.talkers should contain(testActor)
conversationRef ! Leave
conversationActor.talkers should have size 0
conversationActor.talkers should not contain testActor
}
"notify interlocutors when a talker is typing" in new ConversationTestHelper {
val talker1 = TestProbe()
val talker2 = TestProbe()
talker1.send(conversationRef, Join)
talker2.send(conversationRef, Join)
val talker2Id = TalkerIdStub.random
talker2.send(conversationRef, TalkerTyping(talker2Id))
talker1.expectMsgPF() {
case InterlocutorTyping(talkerIdWhoTyped) if talkerIdWhoTyped == talker2Id => true
}
talker2.expectNoMsg()
}
"notify interlocutors when a talker stop typing" in new ConversationTestHelper {
val talker1 = TestProbe()
val talker2 = TestProbe()
talker1.send(conversationRef, Join)
talker2.send(conversationRef, Join)
val talker2Id = TalkerIdStub.random
talker2.send(conversationRef, TalkerStopTyping(talker2Id))
talker1.expectMsgPF() {
case InterlocutorStopTyping(talkerIdWhoStopTyping) if talkerIdWhoStopTyping == talker2Id => true
}
talker2.expectNoMsg()
}
}
}
A future from the main method of a program sends a msg to its actor asking for an iterable object. The actor then creates another future that asks for the iterable object (say an ArrayBuffer) from a remote actor. After receiving the ArrayBuffer from the remote actor, how would the actor send it back to the first future in the main method? It seems creating a local alias of sender and creating a separate case class to represent the iterable does not prevent dead letters from being encountered.
Here is a sample code:
case class SequenceObject(sqnce:Seq[someArrayBuffer])
//...
implicit val timeout = Timeout(10 seconds)
val fut1: Future[Any] = myActor ? iNeedAnArrayBufferObject
fut1.onSuccess {
case listOfItems: SequenceObject => {
//do sth with listofItems.sqnce
}
class myActor extends Actor {
implicit val timeout = Timeout(1 seconds)
def receive = {
case a: iNeedAnArrayBufferObject => {
val originalSender = sender
val fut: Future[Any] = (remoteActor ? a)
fut.onSuccess {
case list: SequenceObject => {
originalSender ! SequenceObject(list.sqnce)
}
}
The remote actor code is:
class ServerActorClass extends Actor {
def receive = {
case a: iNeedAnArrayBufferObject => {
val closer = sender()
closer ! SequenceObject(ArrayBufferObject[information])
}
}
The above does not seem to work. The remote actor and the local actor can communicate and messages are received correctly. However, the iterable object is never send back to fut1. Why is that? Thanks in advance.
Check pipeTo pattern in Ask: Send-And-Receive-Future section