Akka ClusterSingletonProxy to a remote deployed singleton - scala

I'm trying to send a message to a singleton actor that was deployed on a remote node through another actor.
This is the manager that is waiting for a memberUp event, then deploys Worker actor on that node and then sends the singleton a message:
object Manager extends App {
val sys = ActorSystem("mySys", ConfigFactory.load("application").getConfig("manager"))
sys.actorOf(Props[Manager], "manager")
}
class Manager extends Actor with ActorLogging {
override def receive: Receive = {
case MemberUp(member) if member.address != Cluster(context.system).selfAddress =>
context.system.actorOf(ClusterSingletonManager.props(
singletonProps = Props(classOf[Worker]),
singletonName = "worker",
terminationMessage = End,
role = Some("worker")).withDeploy(Deploy(scope = RemoteScope(member.address))))
context.actorOf(ClusterSingletonProxy.props(
singletonPath = s"/user/singleton/worker",
role = Some(s"worker")), "worker") ! "hello"
}
override def preStart(): Unit = {
Cluster(context.system).subscribe(self,classOf[MemberUp])
}
}
This is the worker:
object Worker extends App{
ActorSystem("mySys", ConfigFactory.load("application").getConfig("worker"))
}
class Worker extends Actor with ActorLogging {
override def receive: Receive = {
case msg =>
println(s"GOT MSG : $msg from : ${sender().path.name}")
}
}
And the application.conf:
manager {
akka {
actor {
provider = "akka.cluster.ClusterActorRefProvider"
}
cluster {
auto-down-unreachable-after = 20s
seed-nodes = [
"akka.tcp://mySys#127.0.0.1:2552"
]
roles.1 = "manager"
}
remote.netty.tcp.port = 2552
}
}
worker {
akka {
cluster {
auto-down-unreachable-after = 20s
seed-nodes = [
"akka.tcp://mySys#127.0.0.1:2552"
]
roles.1 = "worker"
}
remote.netty.tcp.port = 2554
actor {
provider = "akka.cluster.ClusterActorRefProvider"
}
}
}
The worker is initialized (and I can see in the logs the state change [Start -> Oldest] message) but the message sent from the manager never arrives to the worker. It used to work fine when I was deploying the singleton on the remote node, but now I want the manager the deploy it.
I also tried to deploy it as the child of the manager (using context instead of context.system) and changed the singleton path to user/manager/singleton/worker, but it didn't work.
I'm using Akka 2.3.11
Edit:
sbt file:
name := "MyProject"
version := "1.0"
scalaVersion := "2.10.5"
libraryDependencies +=
"com.typesafe.akka" %% "akka-actor" % "2.3.11",
"com.typesafe.akka" %% "akka-cluster" % "2.3.11",
"joda-time" % "joda-time" % "2.0",
"com.typesafe.akka" %% "akka-contrib" % "2.3.11"

So I played around a bit with different options of creating ClusterSingletonManagers and I think deploying them remotely is breaking something within the singleton pattern. I have gathered a few indicators for this:
Since it is a remote deployment the path of the ClusterSingletonManager on the worker node is /remote/akka.tcp/mySys#127.0.0.1:2552/user/worker. I don't think the library can / will handle this, since it expects /user/worker
When trying to send the message from the master node using ClusterSingletonProxy log in DEBUG mode states No singleton available, stashing message hello worker and Trying to identify singleton at akka.tcp://mySys#127.0.0.1:2552/user/worker/singleton (which fails and retries) -> It is looking for the singleton on the wrong node, since no manager is available and it is apparently not aware that the singleton is on the worker node.
When creating the ClusterSingletonManager on the worker node directly everything works as expected.
You also had an issue with your naming of the manager. Your singletonName is worker and your manager itself (the actor) does not have any name. When you create the proxy you use the path /user/singleton/worker, but the path should be as follows: /user/{actorName}/{singletonName}. So in my code I used worker as the actorName and singleton as the singletonName.
So here's my working code:
object Manager extends App {
val sys = ActorSystem("mySys", ConfigFactory.load("application").getConfig("manager"))
sys.actorOf(Props[Manager], "manager")
}
class Manager extends Actor with ActorLogging {
override def receive: Receive = {
case MemberUp(member) if member.address != Cluster(context.system).selfAddress =>
context.actorOf(ClusterSingletonProxy.props(
singletonPath = s"/user/worker/singleton",
role = Some("worker")), name = "workerProxy") ! "hello worker"
}
override def preStart(): Unit = {
Cluster(context.system).subscribe(self,classOf[MemberUp])
}
}
object Worker extends App{
val sys = ActorSystem("mySys", ConfigFactory.load("application").getConfig("worker"))
sys.actorOf(ClusterSingletonManager.props(
singletonProps = Props(classOf[Worker]),
singletonName = "singleton",
terminationMessage = PoisonPill,
role = Some("worker")), name = "worker")
}
class Worker extends Actor with ActorLogging {
override def receive: Receive = {
case msg =>
println(s"GOT MSG : $msg from : ${sender().path.name}")
}
}
application.conf and build.sbt stayed the same.
EDIT
Got it to work with by referencing the ClusterSingletonProxy with the actual path on the worker node (calculating in that it is a network path). I am not sure if I would recommend this, since I am still not sure, if that library is designed to be able to do this, but it works at least in this minimal example:
object Manager extends App {
val sys = ActorSystem("mySys", ConfigFactory.load("application").getConfig("manager"))
sys.actorOf(Props[Manager], "manager")
}
class Manager extends Actor with ActorLogging {
override def receive: Receive = {
case MemberUp(member) if member.address != Cluster(context.system).selfAddress =>
val ref = context.system.actorOf(ClusterSingletonManager.props(
singletonProps = Props(classOf[Worker]),
singletonName = "singleton",
terminationMessage = PoisonPill,
role = Some("worker")).withDeploy(Deploy(scope = RemoteScope(member.address))), name = "worker")
context.actorOf(ClusterSingletonProxy.props(
singletonPath = s"${ref.path.toStringWithoutAddress}/singleton", // /remote/akka.tcp/mySys#127.0.0.1:2552/user/worker/singleton
role = Some("worker")), name = "workerProxy") ! "hello worker"
}
override def preStart(): Unit = {
Cluster(context.system).subscribe(self,classOf[MemberUp])
}
}
object Worker extends App{
val sys = ActorSystem("mySys", ConfigFactory.load("application").getConfig("worker"))
}
class Worker extends Actor with ActorLogging {
override def receive: Receive = {
case msg =>
println(s"GOT MSG : $msg from : ${sender().path.name}")
}
}

Related

Akka actor does not receive message with DistributedPubSub

I try to make akka cluster with distributed messages work, but I'm stuck. My actor is properly started and subscribed to topic but no messages are received. Here is the code
import akka.actor.{Actor, ActorSystem, Props}
import akka.cluster.client.ClusterClient.Publish
import akka.cluster.pubsub.DistributedPubSub
import akka.cluster.pubsub.DistributedPubSubMediator.{Subscribe, SubscribeAck}
case object DistributedMessage
object ClusterExample extends App {
val system = ActorSystem("ClusterSystem")
val actor = system.actorOf(Props(classOf[ClusterExample]), "clusterExample")
}
class ClusterExample extends Actor {
private val mediator = DistributedPubSub(context.system).mediator
mediator ! Subscribe("content", self)
override def receive = {
case SubscribeAck(Subscribe("content", None, `self`)) =>
(1 to 100) foreach (_ => {
mediator ! Publish("content", msg = DistributedMessage)
})
case DistributedMessage => println("received message from queue!")
}
}
And here is configuration:
akka {
log-dead-letters = 0
log-dead-letters-during-shutdown = on
actor {
provider = "akka.cluster.ClusterActorRefProvider"
enable-additional-serialization-bindings = on
}
remote {
log-remote-lifecycle-events = off
netty.tcp {
hostname = "127.0.0.1"
port = 2552
bind-hostname = "0.0.0.0"
bind-port = 2552
}
}
extensions = ["akka.cluster.pubsub.DistributedPubSub"]
cluster {
seed-nodes = [
"akka.tcp://ClusterSystem#127.0.0.1:2552"
]
}
}
"received message from queue" is actually never printed
Silly mistake. So, the problem was invalid import. This:
import akka.cluster.client.ClusterClient.Publish
should be replaced by:
import akka.cluster.pubsub.DistributedPubSubMediator.Publish

Cannot configure Bounded Mailbox for Routees of RoundRobinPool

When I try to set bounded-mailbox for routees of a pool (RoundRobinPool) in configuration file, somehow Akka ignores the mailbox configuration.
Here is the configuration I use:
bounded-mailbox {
mailbox-type = "akka.dispatch.BoundedMailbox"
mailbox-capacity = 1
mailbox-push-timeout-time = 1s
}
akka.actor.deployment {
/singletestactor {
mailbox = bounded-mailbox
}
/groupedtestactor {
mailbox = bounded-mailbox
router = round-robin-pool
nr-of-instances = 5
}
}
And here is the test code:
object MailboxTest {
def main(args: Array[String]): Unit = {
val actorSystem = ActorSystem()
val singleTestActor = actorSystem.actorOf(Props[TestActor], "singletestactor")
for (i <- 1 to 10) {
singleTestActor ! Hello(i)
}
val groupedTestActor = actorSystem.actorOf(Props[TestActor].withRouter(FromConfig, "groupedtestactor")
for (i <- 1 to 1000) {
groupedTestActor ! Hello(i)
}
}
}
class TestActor extends Actor {
def receive = {
case Hello(i) => {
println(s"Hello($i) - begin!")
Thread.sleep(10000)
println(s"Hello($i) - end!")
}
}
}
case class Hello(i: Int)
Am I doing something wrong, or there is no way to define mailbox for routees?
You need to add mailbox.requirements configuration in application.conf;
akka.actor.mailbox.requirements {
"akka.dispatch.BoundedMessageQueueSemantics" = bounded-mailbox
}
Then need to extend TestActor like this;
class TestActor extends Actor with RequiresMessageQueue[BoundedMessageQueueSemantics]
See the documentation here for mailbox configuration.
I also created round robin pool like this;
val groupedTestActor = actorSystem.actorOf(FromConfig.props(Props[TestActor]), "groupedtestactor")

Scala: server and Worker actor bind but fail to exchange messages?

I am trying to run a server and remote actor. The two are up and running successfully. However, the server does not receive the message sent by the remote worker.
import akka.actor._
import akka.actor.Props
import akka.event.Logging
import com.typesafe.config.ConfigFactory
import java.security.MessageDigest
import akka.actor.{Actor, ActorSystem, Props}
import akka.routing.RoundRobinRouter
object Project {
// Define cases
case class register()
case class remoteWorkerActive()
// Main that accepts String argument to determine if the actor
// is the server or a worker
def main(args: Array[String]) {
println("I have " + args.length + " argument(s)")
println(args(0))
// Declare configurations for server and worker remote akka actors
//Attach configuration file of the Server
val serverConfiguration = ConfigFactory.parseString(
"""
akka{
actor{
provider = "akka.remote.RemoteActorRefProvider"
}
remote{
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp{
hostname = "127.0.0.1"
port = 2575
}
}
}""")
//Attach configuration file of the Worker
val workerConfiguration = ConfigFactory.parseString(
"""
akka{
actor{
provider = "akka.remote.RemoteActorRefProvider"
}
remote{
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp{
hostname = "127.0.0.1"
port = 0
}
}
}""")
// Based on the input argument, declare an actor system as either
// Server or Worker
if (!args(0).isEmpty) {
//Check if the argument is a valid IP address for a worker
if (args(0).contains('.')) {
//Create the Worker ActorSystem with the above configuration
val system = akka.actor.ActorSystem(
"Remote", ConfigFactory.load(workerConfiguration))
//Create the worker actor
val remote = system.actorOf(Props(
new Remote(args(0))), name = "remote")
remote ! register()
println("This actor is a worker")
}
else {
//Create the Server ActorSystem with the above configuration
val system = akka.actor.ActorSystem(
"Server", ConfigFactory.load(serverConfiguration))
//Create the server actor
val server = system.actorOf(Props[Server], name = "server")
println("This actor is a server")
server ! "test successful";
}
}
}
class Server extends Actor {
def receive = {
case remoteWorkerActive() =>
println("Registration of worker successful")
sender ! "You are a registered worker"
case msg: String =>
println(s"'$msg'")
case _ =>
println("Invalid message")
}
}
class Remote(ip_address: String) extends Actor {
println(ip_address)
val master = context.actorSelection(
"akka.tcp://Server#" + ip_address + ":2575/user/Server")
def receive = {
case register() =>
println("Trying to register with the server")
master ! remoteWorkerActive()
println("Message sent")
case msg: String =>
println(s"'$msg'")
}
}
}
What am I doing wrong? Also, is there any way I can track the messages being sent? Mostly for debugging.
You have:
val server = system.actorOf(Props[Server], name = "server")
and:
val master = context.actorSelection(
"akka.tcp://Server#" + ip_address + ":2575/user/Server")
If Akka ActorPaths are case sensitive, this will fail. Try name = "Server"

Where is wrong in my remote actor demo?

I'm trying to send messages to a remote actor, but failed.
My main code is:
RemoteActorDemo.scala
import akka.actor.{Actor, ActorSystem, Props}
object RemoteActorDemo extends App {
val system = ActorSystem("RemoteActorSystem")
val actor = system.actorOf(Props[RemoteActor], name = "RemoteActor")
actor ! "Remote Actor is alive"
}
class RemoteActor extends Actor {
override def receive: Receive = {
case msg =>
println("### RemoteActor received message: " + msg)
sender ! "Hello from RemoteActor"
}
}
With application.conf:
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 5150
}
}
}
And LocalActorDemo.scala:
import akka.actor.{Actor, ActorSystem, Props}
object LocalActorDemo extends App {
val system = ActorSystem("ActorDemo")
val localActor = system.actorOf(Props[LocalActor])
localActor ! "Start"
}
class LocalActor extends Actor {
val remote = context.actorSelection("akka.tcp://RemoteActorSystem#127.0.0.1:5150/user/RemoteActor")
override def receive: Receive = {
case "Start" =>
println("### LocalActor started")
remote ! "Hello from LocalActor"
case msg => println("*** LocalActor receives msg: " + msg)
}
}
The problem is the local actor can't connect the remote one. It prints in console:
### LocalActor started
[INFO] [10/05/2015 20:57:57.334] [ActorDemo-akka.actor.default-dispatcher-4] [akka://ActorDemo/deadLetters]
Message [java.lang.String] from Actor[akka://ActorDemo/user/$a#-11944341] to Actor[akka://ActorDemo/deadLetters]
was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration
settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
I'm new to akka, not sure where is wrong.
You can see the demo project here: https://github.com/freewind/remote-actors-demo, you can just clone and run it as "README" describes.
Add an application.conf in your local subproject with content like this:
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "127.0.0.1"
port = 0
}
}
}
As the official document says:
To enable remote capabilities in your Akka project you should, at a minimum, add the following changes to your application.conf file
This applied to the client side of a remoting system as well.

Play framework 2.0 - deadLetters instead of an Actor

For learning purposes I'm trying to implement a simple play application that gets data from a remote actor. The code for the actor is as follows:
import akka.actor.{Props, ActorSystem, Actor}
class NumbersServer extends Actor {
var number = 0
protected def receive = {
case 'next => {
number += 1
number
}
case 'reset => number = 0
case 'exit => context.stop(self)
case 'get => sender ! number
}
}
object Server {
def main(args: Array[String]) {
val system = ActorSystem("ServerSystem")
val server = system.actorOf(Props[NumbersServer], "server")
}
}
I package it into a jar and start it from the command line. If I try to send messages to this actor from a Scala console opened from another window, all works fine. Now I want to get the actor from the Play framework. In the Application object I define the following method:
def numbers = Action {
Ok(views.html.numbers(Client.actor.path.name))
}
Then in the models package I define the Client object:
object Client {
import play.api.Play.current
val actor = Akka.system.actorFor("akka://ServerSystem#127.0.0.1:2552/user/server")
}
The numbers.html.scala file:
#(message: String)
#main("Header") {
<h1>#message</h1>
}
So I expect that when I go to 127.0.0.1:9000/numbers, I'd get a page with the path to the server actor. Instead of this, I get <h1>deadLetters</h1>. What do I do wrong and how this should be done correctly?
Please follow the configuration given in
https://groups.google.com/forum/#!topic/akka-user/Vw-B8nQeagk
And also add akka-remote dependency
val appDependencies = Seq(
"com.typesafe.akka" % "akka-remote" % "2.0.2"
)