PersistentActor doesn't receive command - scala

I have implemented the following PersistedActor
import akka.actor._
import akka.persistence._
object TaskDispatcher {
/**
* Create Props for the actor
*/
def props(): Props = Props(new TaskDispatcher())
case class AddEndpoint(serverEndpoint: ActorRef, id: String)
}
class TaskDispatcher() extends PersistentActor with ActorLogging {
import TaskDispatcher._
override def persistenceId = "task-dispatcher-persistence-ID"
// Actor State
var endpoints: Map[String, ActorRef] = Map()
def receiveRecover: Receive = {
case AddEndpoint(serverEndpoint, id) =>
endpoints += (id -> serverEndpoint)
}
def receiveCommand: Receive = {
case AddEndpoint(serverEndpoint, id) =>
log.info("AddEndpoint received")
persistAsync(AddEndpoint(serverEndpoint, id)) { command =>
endpoints += (id -> serverEndpoint)
}
}
}
I create an instance of the PersistedActor and I send a message AddEndpoint to it through another actor
val taskDispatcher =
context.actorOf(Props[TaskDispatcher], "task-dispatcher")
taskDispatcher ! AddEndpoint(self, id)
Before I had a non- persistent version of this actor and everything worked. Now the actor doesn't receive AddEndpoint message. I noticed it because log doesn't print "AddEndpoint received" message. What am I doing wrong?

Maybe just recovery doesn't start. Try place:
override def preStart() = {
self ! Recover()
}

Related

How to use Logging into Akka actor?

Can I set Logging in Akka actor by implicit? I would like to log inside the actor. My code is as given below:
object EmployeeRouterActor {
final case class Employee(id: Long, name: String)
final case object StopChild
final case class ChildResponse(id: Long, name: String)
}
final class EmployeeRouterActor extends Actor with akka.actor.ActorLogging {
import EmployeeRouterActor._
private var children = Map.empty[Long, ActorRef]
override def receive: Receive = {
case e # Employee(id, _) => {
getChild(id) ! e
}
case r # ChildResponse(id, _) => {
stopChild(id)
val idItem = r.id
val nameItem = r.name
sender ! s"Employee {$idItem} is $nameItem."
}
}
private def getChild(id: Long): ActorRef =
context.child(id.toString).getOrElse {
val child = context.actorOf(EmployeeEchoActor.apply(), id.toString)
children += (id -> child)
child
}
private def stopChild(id: Long) = {
children(id) ! StopChild
children -= id
}
}
But, in this case, I can not do something like this:
class EmployeeRouterActor (implicit logger: LoggingAdapter) { ... }
I have got following errors:
Error:(10, 30) could not find implicit value for parameter logger: akka.event.LoggingAdapter
def apply(): Props = Props(new EmployeeEchoActor)
Error:(10, 30) not enough arguments for constructor EmployeeEchoActor: (implicit logger: akka.event.LoggingAdapter)actors.EmployeeEchoActor.
Unspecified value parameter logger.
def apply(): Props = Props(new EmployeeEchoActor)
Is it possible to use Logging from Akka inside the actors?
EmployeeEchoActor (child actor) is defined as given below:
object EmployeeEchoActor {
def apply(): Props = Props(new EmployeeEchoActor)
}
class EmployeeEchoActor(implicit logger: LoggingAdapter) extends Actor {
override def receive = {
case employee: EmployeeRouterActor.Employee => {
logger.info("Message received!")
val idItem = employee.id
val nameItem = employee.name
context.parent ! EmployeeRouterActor.ChildResponse( idItem, nameItem )
}
case EmployeeRouterActor.StopChild => {
logger.info("Stopping :(!")
context.stop(self)
}
case _ => sender ! "Internal Error!"
}
}
The solution is:
final class EmployeeRouterActor extends Actor with akka.actor.ActorLogging {
Implement ActorLogging and use log at code, for example:
override def receive: Receive = {
case e # Employee(id, _) => {
log.info("Message received!")
getChild(id) ! e
}
case r # ChildResponse(id, _) => {
log.info("Response received from child!")
stopChild(id)
log.info("Child has been stopped!")
val idItem = r.id
val nameItem = r.name
sender ! s"Employee {$idItem} is $nameItem."
}
}

akka sending a closure to remote actor + Error in sending messages (case class) between Scala Akka remote actors

New query: I am trying to pass DSum() as parameter to RemoteActor from localActor, DSum() will do some calculation at Remote node. I am unable to send this to RemoteActor. IS it possible ??(code below)
Done:I am trying to connect Remote actor and local actor, and trying to send objects using case class, but it is unable to get the Message class ( Common.Message(msg) ) of the RemoteActor when being called from localActor, instead it is getting "case _ => println("Received unknown msg from local ")"
1.package.scala
package object check {
trait Context
case object Start
case class Message(msg: String)
case class CxtDA(cxtA: List[CxtA])
case class RCxt(var cxtA: List[CxtA], var cxtB: List[CxtB], var v1: Int, var v2: String) extends Context
case class CxtA(var cxtC: List[CxtC], var v1: Int) extends Context
case class CxtB(var cxtC: List[CxtC], var v1: Int) extends Context
case class CxtC(var v1: String, var v2: Int) extends Context
case class Task(var t1: DSum())
}
2. Remote Actor
package com.akka.remote
import java.io.File
import akka.actor._
import com.typesafe.config.ConfigFactory
import check._
/**
* Remote actor which listens on port 5150
*/
class RemoteActor extends Actor {
override def toString: String = {
return "You printed the Local";
}
def receive = {
case msg: String => {
println("remote received " + msg + " from " + sender)
sender ! "hi"
}
case Message(msg) =>
println("RemoteActor received message "+ msg)
sender ! Message("Hello from server")
case CxtDA(cxtA) =>
println("cxtA "+ cxtA)
case Task(taskA) =>
println ("recieved closure")
case _ => println("unknown msg")
}
}
object RemoteActor{
def main(args: Array[String]) {
//get the configuration file from classpath
val configFile = getClass.getClassLoader.getResource("remote_application.conf").getFile
// //parse the config
val config = ConfigFactory.parseFile(new File(configFile))
// //create an actor system with that config
val system = ActorSystem("RemoteSystem" , config)
// //create a remote actor from actorSystem
val remoteActor = system.actorOf(Props[RemoteActor], name="remote")
println("remote is ready")
remoteActor ! Message("Hello from active remote")
}
}
3.Local Actor
package com.akka.local
import java.io.File
import akka.actor.{Props, Actor, ActorSystem}
import com.typesafe.config.ConfigFactory
import check._
import scala.util.Random
/**
* Local actor which listens on any free port
*/
trait CxtTask {
type CxtT <: Context
def work(ctx: CxtT): CxtT
}
class DSum extends CxtTask with Serializable{
override type CxtT = CxtA
def work(ctx: CxtA): CxtA = {
val sum = ctx.cxtC.foldRight(0)((v, acc) => v.v2 + acc)
ctx.cxtC= List()
ctx.v1 = sum
println("ctx: " + ctx)
ctx
}
}
class LocalActor extends Actor{
// import Common._
#throws[Exception](classOf[Exception])
val remoteActor = context.actorSelection("akka.tcp://RemoteSystem#127.0.0.1:5150/user/remote")
println("That 's remote:" + remoteActor)
remoteActor ! "hi"
var counter = 0
override def toString: String = {
return "You printed the Local";
}
def receive = {
case msg:String => {
println("got message from remote" + msg)
}
case Start =>
println("inside Start.local "+ remoteActor)
remoteActor ! Message("Hello from the LocalActor")
case Message(msg) =>
println("LocalActor received message: "+ msg)
if (counter < 5) {
sender ! Message("Hello back to you")
counter += 1
}
case CxtDA(cxtA) =>
remoteActor ! CxtDA(cxtA)
case Task(t1) =>
remoteActor ! Task(t1)
}
}
object LocalActor {
def main(args: Array[String]) {
val configFile = getClass.getClassLoader.getResource("local_application.conf").getFile
val config = ConfigFactory.parseFile(new File(configFile))
val system = ActorSystem("ClientSystem",config)
val localActor = system.actorOf(Props[LocalActor], name="local")
localActor ! Start
def createRndCxtC(count: Int):List[CxtC] = (for (i <- 1 to count) yield CxtC(Random.nextString(5), 3)).toList
def createRndCxtB(count: Int): List[CxtB] = (for (i <- 1 to count) yield CxtB(createRndCxtC(count), Random.nextInt())).toList
def createRndCxtA(count: Int): List[CxtA] = (for (i <- 1 to count) yield CxtA(createRndCxtC(count), Random.nextInt())).toList
val tree = RCxt(createRndCxtA(2),createRndCxtB(2),1,"")
val workA = new DSum()
tree.cxtA.foreach(ctxa =>workA.work(ctxa))
localActor ! Task(new DSum())
}
}
[Remote actor output][1]
[1]: https://i.stack.imgur.com/mtmvU.jpg
The key thing here is that you have defined two different protocols for each actor:
Common object that resides in the RemoteActor.scala file
Common object that resides in the LocalActor.scala file
Hence, when sending a Common.Message within the Local Actor, you are basically creating a message with a different type than the Common.Message from the Remote Actor. Hence, the actor is not able to process it.
As a good practice in Akka, whenever an actor has a specific message procotol, that should be defined in its companion object. However, if you have multiple actors that share the same protocol (their behavior is defined by processing those types of messages), then you should put that protocol in an object and import it from your actors.
I hope this is helpful.

Implementing map entry listener

I'm trying to implement Map entry listener with Scala.
Idea:
I need to subscribe to the Map from Service.
I need to notify all subscribers of the Map when entry with specific key is added/updated.
I need to have access to the Map from other Services to check entry value.
I couldn't find ready solution for this, so I've tried to implement it with Akka:
class TrackingService(system: ActorSystem) extends LazyLogging {
private val trackingActor = system.actorOf(TrackingActor.props)
private val duration = Duration(15, TimeUnit.SECONDS)
private implicit val timeout = Timeout(duration)
def fireEvent(key: String): Unit = {
TrackingActor ! EventFired(key)
}
def eventHappened(key: String): Future[Boolean] = {
TrackingActor ? DoesEventHappened(key)).mapTo[Boolean]
}
def registerHiddenCardListener(key: String, data: MyData): Unit = {
TrackingActor ! Subscribe(key, data)
}
}
case class EventFired(key: String)
case class EventHappened(key: String)
case class EventHappenedResponse(happened: Boolean)
case class Subscribe(key: String, data: Data)
class TrackingActor extends Actor with LazyLogging {
var eventMap: Map[String, Boolean] = Map.empty[String, Boolean]
var dataMap: Map[String, List[Data]] = Map.empty[String, List[Data]]
def receive: Receive = {
case Subscribe(key, data) =>
val currentData: List[Data] = dataMap.getOrElse(key, Nil)
val newData = data :: currentData
dataMap = dataMap + (key -> newData)
case EventHappened(key) => sender() ! EventHappenedResponse(eventMap.getOrElse(key, false))
case e#EventFired(key) =>
eventMap = eventMap + (key -> true)
for {
dataOpt <- dataMap.get(key)
data <- dataOpt
} {
// do callback with data (e.g. send email)
}
case x => logger.warn(s"Received unknown message: $x")
}
}
object TrackingActor {
def props: Props = Props(classOf[TrackingActor])
}
What I don't like in this solution: I don't like ask pattern, but I need to have access to the entries from non-actor classes. Also, I don't like having 2 maps, but I need to store somewhere data, which should be used for the callback.
Any ideas on how can I improve this?
Here's an idea:
case class Subscribe[A, B](f: (A, B, NotifyingMap[A,B]) => Any)
case class Event[A, B](key: A, value: B, originator: NotifyingMap[A,B])
case class RegisterObserver(actorRef: ActorRef)
/**
* Subscribes to events
*/
class Subscriber[A,B]{
def register(actorSystem: ActorSystem) = {
val actor = actorSystem.actorOf(Props(classOf[Observer[A,B]]))
actor ! Subscribe(handleEvent)
}
def handleEvent(key: A, value: B, notifyingMap: NotifyingMap[A, B]) = {
println(s"Saw key $key with value $value")
}
}
/**
* Observer of events that will call a partial function when
* an event comes in.
*/
class Observer[A, B] extends Actor{
var f: (A,B,NotifyingMap[A,B]) => Any = _
def receive = {
case x: Subscribe[A, B] =>
f = x.f
Notifier() ! RegisterObserver(self)
case e: Event[A,B] =>
f(e.key, e.value, e.originator)
}
}
/**
* Notifier that sends out the event to all registered observers.
*/
class Notifier extends Actor {
var observers = List[ActorRef]()
def receive = {
case x: RegisterObserver =>
observers = x.actorRef :: observers
case x: Event[_,_] =>
observers.foreach(_ ! Event)
}
}
/**
* Singleton notifier.
*/
object Notifier{
var notifier: ActorRef = _
def create(actorSystem: ActorSystem) =
actorSystem.actorOf(Props(classOf[Notifier]))
def apply(): ActorRef = notifier
}
/**
* Class that sends out an event when an item is put. Also allows for
* getting an item based on a key.
*/
class NotifyingMap[A, B](){
val map: TrieMap[A,B] = TrieMap[A,B]()
// Specific business logic here on when you publish the event.
def put(key: A, value: B) = {
map.put(key, value).foreach(v => Notifier() ! Event(key, v, this))
}
def get(key: A) = map.get(key)
}
By doing this you can keep your Subscriber a non-Actor class while still allowing it to react to an event. You can also call plain old methods on your NotifyingMap since it's just a class and not an Actor.
I personally like to store callback information in the messages. Typically you see this by having another ActorRef in a case class. In this example we have the NotifyingMap in the case class so we know where the event originated from and can appropriately call the get method there.
Full disclosure: I didn't run any of this code. It does compile.

Akka consolidate concurrent database requests

I want to be able to make concurrent requests to multiple data repositories and consolidate the results. I am trying to understand if my approach is at all valid or if there is a better way to approach this problem. I am definitely new to Akka / Spray / Scala and really want to get a better understanding of how to properly build these components. Any suggestions / Tips would be greatly appreciated. Trying to wrap my head around the use of actors and futures for this type of implementation.
Spray Service:
trait DemoService extends HttpService with Actor with ActorLogging {
implicit val timeout = Timeout(5 seconds) // needed for `?` below
val mongoMasterActor = context.actorOf(Props[MongoMasterActor], "redisactor")
val dbMaster = context.actorOf(Props[DbMasterActor], "dbactor")
val messageApiRouting =
path("summary" / Segment / Segment) { (dataset, timeslice) =>
onComplete(getDbResponses(dbMaster, dataset, timeslice)) {
case Success(dbMessageResponse) => complete(s"The result was $dbMessageResponse")
case Failure(ex) => complete(s"An error occurred: ${ex.getMessage}")
}
}
/** Passes the desired actor reference for a specific dataset and timeslice for summary data retrieval
*
* #param mongoActor an actor reference to the RedisActor that will handle the appropriate request routing
* #param dataset The dataset for which the summary has been requested
* #param timeslice The timeslice (Month, Week, Day, etc.) for which the summary has been requested
*/
def getSummary(mongoActor: ActorRef, dataset: String, timeslice: String): Future[DbMessageResponse] = {
log.debug(s"dataset: $dataset timeslice: $timeslice")
val dbMessage = DbMessage("summary", dataset + timeslice)
(mongoActor ? dbMessage).mapTo[DbMessageResponse]
}
def getDbResponses(dbActor: ActorRef, dataset: String, timeslice: String): Future[SummaryResponse] = {
log.debug(s"dataset: $dataset timeslice: $timeslice")
val dbMessage = DbMessage("summary", dataset + timeslice)
(dbActor ? dbMessage).mapTo[SummaryResponse]
}
def getSummaryPayload(mongoSummary: DbMessageResponse, redisSummary: DbMessageResponse): String = {
mongoSummary.response + redisSummary.response
}
}
Akka Actor / Future mock db requests:
class DbMasterActor extends Actor with ActorLogging {
private var originalSender: ActorRef = _
//TODO: Need to add routing to the config to limit instances
val summaryActor = context.actorOf(Props(new SummaryActor), "summaryactor")
def receive = {
case msg: DbMessage => {
this.originalSender = sender
msg.query match {
case "summary" => {
getDbResults().onComplete{
case Success(result) => originalSender ! result
case Failure(ex) => log.error(ex.getMessage)
}
}
}
}
//If not match log an error
case _ => log.error("Received unknown message: {} ")
}
def getDbResults(): Future[SummaryResponse] = {
log.debug("hitting db results")
val mongoResult = Future{ Thread.sleep(500); "Mongo"}
val redisResult = Future{ Thread.sleep(800); "redis"}
for{
mResult <- mongoResult
rResult <- redisResult
} yield SummaryResponse(mResult, rResult)
}
}
Following the reading of Effective Akka by Jamie Allen, I am going to attempt to apply his "Cameo" pattern suggestion.
Slideshare:
http://www.slideshare.net/shinolajla/effective-akka-scalaio
Github:
https://github.com/jamie-allen/effective_akka
I think what I created will work, but doesn't sound like the best approach based on Jamie's comments in his talks. I will update / edit back to this post what I have implemented (or try to).
Summary Actor (Cameo Actor):
object SummaryResponseHandler {
case object DbRetrievalTimeout
def props(mongoDb: ActorRef, redisDb: ActorRef, originalSender: ActorRef): Props = {
Props(new SummaryResponseHandler(mongoDb, redisDb, originalSender))
}
}
class SummaryResponseHandler(mongoDb: ActorRef, redisDb: ActorRef,
originalSender: ActorRef) extends Actor with ActorLogging {
import SummaryResponseHandler._
var mongoSummary, redisSummary: Option[String] = None
def receive = LoggingReceive {
case MongoSummary(summary) =>
log.debug(s"Received mongo summary: $summary")
mongoSummary = summary
collectSummaries
case RedisSummary(summary) =>
log.debug(s"Received redis summary: $summary")
redisSummary = summary
collectSummaries
case DbRetrievalTimeout =>
log.debug("Timeout occurred")
sendResponseAndShutdown(DbRetrievalTimeout)
}
def collectSummaries = (mongoSummary, redisSummary) match {
case (Some(m), Some(r)) =>
log.debug(s"Values received for both databases")
timeoutMessager.cancel
sendResponseAndShutdown(DataSetSummary(mongoSummary, redisSummary))
case _ =>
}
def sendResponseAndShutdown(response: Any) = {
originalSender ! response
log.debug("Stopping context capturing actor")
context.stop(self)
}
import context.dispatcher
val timeoutMessager = context.system.scheduler.scheduleOnce(
250 milliseconds, self, DbRetrievalTimeout)
}
class SummaryRetriever(mongoDb: ActorRef, redisDb: ActorRef) extends Actor with ActorLogging {
def receive = {
case GetSummary(dataSet) =>
log.debug("received dataSet")
val originalSender = sender
val handler = context.actorOf(SummaryResponseHandler.props(mongoDb,redisDb, originalSender), "cameo-message-handler")
mongoDb.tell(GetSummary(dataSet), handler)
redisDb.tell(GetSummary(dataSet), handler)
case _ => log.debug(s"Unknown result $GetSummary(datset)")
}
}
Common:
case class GetSummary(dataSet: String)
case class DataSetSummary(
mongo: Option[String],
redis: Option[String]
)
case class MongoSummary(
summary: Option[String]
)
case class RedisSummary(
summary: Option[String]
)
trait MongoProxy extends Actor with ActorLogging
trait RedisProxy extends Actor with ActorLogging
Mock Stubs:
class MongoProxyStub extends RedisProxy {
val summaryData = Map[String, String](
"dataset1" -> "MongoData1",
"dataset2" -> "MongoData2")
def receive = LoggingReceive {
case GetSummary(dataSet: String) =>
log.debug(s"Received GetSummary for ID: $dataSet")
summaryData.get(dataSet) match {
case Some(data) => sender ! MongoSummary(Some(data))
case None => sender ! MongoSummary(Some(""))
}
}
}
class RedisProxyStub extends MongoProxy{
val summaryData = Map[String, String](
"dataset1" -> "RedisData1",
"dataset2" -> "RedisData2")
def receive = LoggingReceive {
case GetSummary(dataSet: String) =>
log.debug(s"Received GetSummary for ID: $dataSet")
summaryData.get(dataSet) match {
case Some(data) => sender ! RedisSummary(Some(data))
case None => sender ! RedisSummary(Some(""))
}
}
}
Boot (You should use test, but was just wanting to run from boot):
object Boot extends App{
val system = ActorSystem("DbSystem")
val redisProxy = system.actorOf(Props[RedisProxyStub], "cameo-success-mongo")
val mongoProxy = system.actorOf(Props[MongoProxyStub], "cameo-success-redis")
val summaryRetrieverActor = system.actorOf(Props(new SummaryRetriever(redisProxy, mongoProxy)), "cameo-retriever1")
implicit val timeout = Timeout(5 seconds)
val future = summaryRetrieverActor ? GetSummary("dataset1")
val result = Await.result(future, timeout.duration).asInstanceOf[DataSetSummary]
println(Some(result.mongo).x)
println(result.redis)
system.shutdown()
}
Application Config:
akka.loglevel = "DEBUG"
akka.event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
akka.actor.debug.autoreceive = on
akka.actor.debug.lifecycle = on
akka.actor.debug.receive = on
akka.actor.debug.event-stream = on

Implement weak-referenced Eventbus actors?

Is it correct to say that unreferenced actors remain subscribed to the event stream ? At least, that's what I get from experimenting with Akka...
I'm trying to implement weak-referencing for actors in an EventBus scenario. In those cases the event listeners/actors typically come and go. Unlike independent actors which are supposed to be present all of the time. Explicit unregistering of course does work. But I'm not always able to perceive the right moment to do this.
Does Akka provide in such a use case ?
val as = ActorSystem.create("weak")
var actor = as.actorOf(Props[ExceptionHandler])
as.eventStream.subscribe(actor,classOf[Exception])
// an event is published & received
as.eventStream.publish(new KnownProblem)
//session expires or whatever that makes the actor redundant
actor = null
(1 to 30).foreach(_ => System.gc)
// an event is published & STILL received
as.eventStream.publish(new KnownProblem)
Okay, I could not actually implement it, but actor is stopping on GC. Using Scala 2.9.2 (REPL) + Akka 2.0.3.
The EventBus with WeakReference[ActorRef] did not help - because in Akka you also have a dungeon with ChildrenContainer (self.children), also there could be Monitor subscriptions to lifecycle events. The thing I did not try - creating actors with dispatcher that only knows about our new shiny WeakEventBus - so maybe I missed the point?
Here goes the code for REPL (start it with appropriate imports, and :paste it in 2 steps):
// Start REPL with something like:
// scala -Yrepl-sync -classpath "/opt/akka-2.0.3/lib/akka/akka-actor-2.0.3.jar:
// /opt/akka-2.0.3/lib/akka/akka-remote-2.0.3.jar:
// /opt/akka-2.0.3/lib/akka/config-0.3.1.jar:
// /opt/akka-2.0.3/lib/akka/protobuf-java-2.4.1.jar:
// /opt/akka-2.0.3/lib/akka/netty-3.5.3.Final.jar"
// :paste 1/2
import akka.actor._
import akka.pattern._
import akka.event._
import akka.util._
import com.typesafe.config.ConfigFactory
import akka.util.Timeout
import akka.dispatch.Await
import scala.ref.WeakReference
import java.util.Comparator
import java.util.concurrent.atomic._
import java.util.UUID
case class Message(val id:String,val timestamp: Long)
case class PostMessage(
override val id:String=UUID.randomUUID().toString(),
override val timestamp: Long=new java.util.Date().getTime(),
text:String) extends Message(id, timestamp)
case class MessageEvent(val channel:String, val message:Message)
case class StartServer(nodeName: String)
case class ServerStarted(nodeName: String, actor: ActorRef)
case class IsAlive(nodeName: String)
case class IsAliveWeak(nodeName: String)
case class AmAlive(nodeName: String, actor: ActorRef)
case class GcCheck()
case class GcCheckScheduled(isScheduled: Boolean,
gcFlag: WeakReference[AnyRef])
trait WeakLookupClassification { this: WeakEventBus ⇒
protected final val subscribers = new Index[Classifier,
WeakReference[Subscriber]](mapSize(),
new Comparator[WeakReference[Subscriber]] {
def compare(a: WeakReference[Subscriber],
b: WeakReference[Subscriber]): Int = {
if (a.get == None || b.get == None) -1
else compareSubscribers(a.get.get, b.get.get)
}
})
protected def mapSize(): Int
protected def compareSubscribers(a: Subscriber, b: Subscriber): Int
protected def classify(event: Event): Classifier
protected def publish(event: Event, subscriber: Subscriber): Unit
def subscribe(subscriber: Subscriber, to: Classifier): Boolean =
subscribers.put(to, new WeakReference(subscriber))
def unsubscribe(subscriber: Subscriber, from: Classifier): Boolean =
subscribers.remove(from, new WeakReference(subscriber))
def unsubscribe(subscriber: Subscriber): Unit =
subscribers.removeValue(new WeakReference(subscriber))
def publish(event: Event): Unit = {
val i = subscribers.valueIterator(classify(event))
while (i.hasNext) publish(event, i.next().get.get)
}
}
class WeakEventBus extends EventBus with WeakLookupClassification {
type Event = MessageEvent
type Classifier=String
type Subscriber = ActorRef
protected def compareSubscribers(a: ActorRef, b: ActorRef) = a compareTo b
protected def mapSize(): Int = 10
protected def classify(event: Event): Classifier = event.channel
protected def publish(event: Event, subscriber: Subscriber): Unit =
subscriber ! event
}
lazy val weakEventBus = new WeakEventBus
implicit val timeout = akka.util.Timeout(1000)
lazy val actorSystem = ActorSystem("serversys", ConfigFactory.parseString("""
akka {
loglevel = "DEBUG"
actor {
provider = "akka.remote.RemoteActorRefProvider"
debug {
receive = on
autoreceive = on
lifecycle = on
event-stream = on
}
}
remote {
transport = "akka.remote.netty.NettyRemoteTransport"
log-sent-messages = on
log-received-messages = on
}
}
serverconf {
include "common"
akka {
actor {
deployment {
/root {
remote = "akka://serversys#127.0.0.1:2552"
}
}
}
remote {
netty {
hostname = "127.0.0.1"
port = 2552
}
}
}
}
""").getConfig("serverconf"))
class Server extends Actor {
private[this] val scheduled = new AtomicBoolean(false)
private[this] val gcFlagRef = new AtomicReference[WeakReference[AnyRef]]()
val gcCheckPeriod = Duration(5000, "millis")
override def preRestart(reason: Throwable, message: Option[Any]) {
self ! GcCheckScheduled(scheduled.get, gcFlagRef.get)
super.preRestart(reason, message)
}
def schedule(period: Duration, who: ActorRef) =
actorSystem.scheduler.scheduleOnce(period)(who ! GcCheck)
def receive = {
case StartServer(nodeName) =>
sender ! ServerStarted(nodeName, self)
if (scheduled.compareAndSet(false, true))
schedule(gcCheckPeriod, self)
val gcFlagObj = new AnyRef()
gcFlagRef.set(new WeakReference(gcFlagObj))
weakEventBus.subscribe(self, nodeName)
actorSystem.eventStream.unsubscribe(self)
case GcCheck =>
val gcFlag = gcFlagRef.get
if (gcFlag == null) {
sys.error("gcFlag")
}
gcFlag.get match {
case Some(gcFlagObj) =>
scheduled.set(true)
schedule(gcCheckPeriod, self)
case None =>
println("Actor stopped because of GC: " + self)
context.stop(self)
}
case GcCheckScheduled(isScheduled, gcFlag) =>
if (isScheduled && scheduled.compareAndSet(false, isScheduled)) {
gcFlagRef.compareAndSet(null, gcFlag)
schedule(gcCheckPeriod, self)
}
case IsAlive(nodeName) =>
println("Im alive (default EventBus): " + nodeName)
sender ! AmAlive(nodeName, self)
case e: MessageEvent =>
println("Im alive (weak EventBus): " + e)
}
}
// :paste 2/2
class Root extends Actor {
def receive = {
case start # StartServer(nodeName) =>
val server = context.actorOf(Props[Server], nodeName)
context.watch(server)
Await.result(server ? start, timeout.duration)
.asInstanceOf[ServerStarted] match {
case started # ServerStarted(nodeName, _) =>
sender ! started
case _ =>
throw new RuntimeException(
"[S][FAIL] Could not start server: " + start)
}
case isAlive # IsAlive(nodeName) =>
Await.result(context.actorFor(nodeName) ? isAlive,
timeout.duration).asInstanceOf[AmAlive] match {
case AmAlive(nodeName, _) =>
println("[S][SUCC] Server is alive : " + nodeName)
case _ =>
throw new RuntimeException("[S][FAIL] Wrong answer: " + nodeName)
}
case isAliveWeak # IsAliveWeak(nodeName) =>
actorSystem.eventStream.publish(MessageEvent(nodeName,
PostMessage(text="isAlive-default")))
weakEventBus.publish(MessageEvent(nodeName,
PostMessage(text="isAlive-weak")))
}
}
lazy val rootActor = actorSystem.actorOf(Props[Root], "root")
object Root {
def start(nodeName: String) = {
val msg = StartServer(nodeName)
var startedActor: Option[ActorRef] = None
Await.result(rootActor ? msg, timeout.duration)
.asInstanceOf[ServerStarted] match {
case succ # ServerStarted(nodeName, actor) =>
println("[S][SUCC] Server started: " + succ)
startedActor = Some(actor)
case _ =>
throw new RuntimeException("[S][FAIL] Could not start server: " + msg)
}
startedActor
}
def isAlive(nodeName: String) = rootActor ! IsAlive(nodeName)
def isAliveWeak(nodeName: String) = rootActor ! IsAliveWeak(nodeName)
}
////////////////
// actual test
Root.start("weak")
Thread.sleep(7000L)
System.gc()
Root.isAlive("weak")