So, when sending a message using the ask pattern (actor ? msg), it creates a "one-off actor" behind the scenes.
My question is - Is it possible to send this temporary actor a message using actorSelection?
For example, the following code works well:
object Test extends App {
case class WrappedMsg(msg: String, replyTo: ActorRef)
class Source(target: ActorRef) extends Actor {
def receive = { case _ => } // doesn't matter
implicit val execution = context.dispatcher
implicit val timeout = Timeout(5.seconds)
val middleware = context.actorOf(Props(new Middleware(target)))
(middleware ? "Something").mapTo[String].onComplete {
case Success(msg) => println("Success: " + msg)
case Failure(err) => println("Failure: " + err)
}
}
class Middleware(target: ActorRef) extends Actor {
def receive = {
case msg: String =>
val wrappedMsg = WrappedMsg(replyTo = sender(), msg = msg)
target ! wrappedMsg
}
}
class Target extends Actor {
def receive = {
case wrappedMsg: WrappedMsg => wrappedMsg.replyTo ! "Received"
}
}
val system = ActorSystem()
val target = system.actorOf(Props(new Target))
val source = system.actorOf(Props(new Source(target)))
}
But, if I make the following changes, in order to use the actor url instead of ActorRef, it fails:
case class WrappedMsg(msg: String, replyTo: String)
...
val wrappedMsg = WrappedMsg(replyTo = sender().path.toSerializationFormat, msg = msg)
...
case wrappedMsg: WrappedMsg => context.actorSelection(wrappedMsg.replyTo) ! "Received"
Thanks
Thanks to #Zernike (see the comments in the question), we've found out that in Akka 2.3.12 it works well - the temporary actor is resolved using actorSelection. (The original code that caused the failure was tested in Akka 2.3.6)
Related
I have a play(2.4.2 which has akka 2.4.18) application in which I am using akka actors for uploading a file. I have a parent supervisor Actor with this kind of hierarchy
UploadSupervisor ---child---> UploadActor ---child--->
DataWriteActor & MetaWriteActor
The leaf actors MetaWriteActor & DataWriteActor does the actual writing. A very simplified version of my code is below:
First I have a actor supervisor:
class UploadSupervisor extends Actor {
val uploadActor = context.actorOf(Props(new UploadActor), "UploadActor")
override def supervisorStrategy = OneForOneStrategy() {
case _: Throwable => Restart
}
override def receive: Receive = {
case data: Data => uploadActor ! data
case meta: MetaInfo => uploadActor ! meta
//How do I send response outside of actor system?
case dataSuccess: DataUploadResponse => ??? //Line 10
case metaSuccess: MetaUploadResponse => ??? //Line 11
}
object UploadSupervisor {
val uploadSupervisor = Akka.system
.actorOf(Props(new UploadSupervisor), "UploadSupervisor")
}
//Request & Response case classes
case class Data(content: String)
case class MetaInfo(id: String, createdDate: Timestamp)
case class DataUploadResponse(location: String)
case class MetaUploadResponse(location: String)
UploadActor:-
class UploadActor extends Actor {
val dataWriteActor = context.actorOf(Props(new DataWriteActor), "dataWriteActor")
val metaWriteActor = context.actorOf(Props(new MetaWriteActor), "UploadActor")
override def receive = {
case data: Data => dataWriteActor ! data
case meta: MetaInfo => metaWriteActor ! meta
case dataResp: DataUploadResponse => context.parent ! dataResp
case metaResp: MetaUploadResponse => context.parent ! metaResp
}
}
DataWriteActor :
class DataWriteActor extends Actor {
case data: Data => //Do the writing
println("data write completed")
sender() ! DataUploadResponse("someLocation")
}
MetaWriteActor
class MetaWriteActor extends Actor {
case meta: MetaInfo=> //Do the writing
println(" meta info writing completed")
sender() ! MetaUploadResponse("someOtherLocation")
}
Somewhere outside Actor system:-
implicit val timeout = Timeout(10 seconds)
val f1 = UploadSupervisor.uploadSupervisor ? Data("Hello Akka").mapTo(implicitly[scala.reflect.ClassTag[DataUploadResponse]])
val f2 = UploadSupervisor.uploadSupervisor ? MetaInfo("1234", new Timestamp(new Date().getTime).mapTo(implicitly[scala.reflect.ClassTag[MetaUploadResponse]])
//Do something with futures
The question is how to send the response outside the actor system? Because in Line 10 & 11, I can't use sender ! msg because the current sender is the UploadActor.
You could keep in UploadSupervisor references to the initial senders:
class UploadSupervisor extends Actor {
val uploadActor = context.actorOf(Props[UploadActor], "UploadActor")
override val supervisorStrategy = OneForOneStrategy() {
case _ => Restart
}
var dataSender: Option[ActorRef] = None
var metaSender: Option[ActorRef] = None
def receive = {
case data: Data =>
val s = sender
dataSender = Option(s)
uploadActor ! data
case meta: MetaInfo =>
val s = sender
metaSender = Option(s)
uploadActor ! meta
case dataSuccess: DataUploadResponse =>
dataSender.foreach(_ ! dataSuccess)
case metaSuccess: MetaUploadResponse =>
metaSender.foreach(_ ! metaSuccess)
}
}
To send messages to UploadSupervisor:
implicit val timeout = Timeout(10 seconds)
val f1 = (UploadSupervisor.uploadSupervisor ? Data("Hello Akka")).mapTo[DataUploadResponse]
val f2 = (UploadSupervisor.uploadSupervisor ? MetaInfo("1234", new Timestamp(new Date().getTime)).mapTo[MetaUploadResponse]
The above assumes that you're sending one Data message and one MetaInfo message to UploadSupervisor at a time. This approach will break down if you send multiple Data and MetaInfo messages and expect concurrent replies. A more general solution is to include the reference to the initial sender in additional case classes that wrap your existing case classes, passing this reference through your actor hierarchy:
case class DataMsg(data: Data, target: ActorRef)
case class MetaInfoMsg(metaInfo: MetaInfo, target: ActorRef)
case class DataUploadMsg(response: DataUploadResponse, target: ActorRef)
case class MetaUploadMsg(response: MetaUploadResponse, target: ActorRef)
class UploadSupervisor extends Actor {
val uploadActor = context.actorOf(Props[UploadActor], "UploadActor")
override val supervisorStrategy = OneForOneStrategy() {
case _ => Restart
}
def receive = {
case data: Data =>
val s = sender
uploadActor ! DataMsg(data, s)
case meta: MetaInfo =>
val s = sender
uploadActor ! MetaInfoMsg(meta, s)
case DataUploadMsg(response, target) =>
target ! response
case MetaUploadMsg(response, target) =>
target ! response
}
}
The UploadActor:
class UploadActor extends Actor {
val dataWriteActor = context.actorOf(Props[DataWriteActor], "dataWriteActor")
val metaWriteActor = context.actorOf(Props[MetaWriteActor], "UploadActor")
def receive = {
case data: DataMsg => dataWriteActor ! data
case meta: MetaInfoMsg => metaWriteActor ! meta
case dataResp: DataUploadMsg => context.parent ! dataResp
case metaResp: MetaUploadMsg => context.parent ! metaResp
}
}
The writers:
class DataWriteActor extends Actor {
def receive = {
case DataMsg(data, target) =>
// do the writing
println("data write completed")
sender ! DataUploadMsg(DataUploadResponse("someLocation"), target)
}
}
class MetaWriteActor extends Actor {
def receive = {
case MetaInfoMsg(meta, target) =>
// do the writing
println("meta info writing completed")
sender ! MetaUploadMsg(MetaUploadResponse("someOtherLocation"), target)
}
}
I'm trying to test my actor logic with AkkaTestKit. The problem is that my actor uses ask pattern. So I need to answer somehow. It look as this:
case class AskExecution(id: Long)
override def receive: Receive = {
case id : Long =>
implicit val dispatcher = context.dispatcher
implicit val timeout = Timeout(10 seconds)
val executor = sender
//How to answer this?
val f = executor ? AskExecution(id) map(v => v.asInstanceOf[Option[Long]])
f.onComplete{
case Success(k) =>
case Failure(_) =>
}
}
In test I use it as follows:
val ca = TestActorRef(new TheActor())
ca ! 0L //I send 0, Now I want to answer the ask
//How to do so?
To make your code easier to test, give your actor a reference to the executor actor (the actor that handles the AskExecution message).
import akka.pattern.pipe
class TheActor(executor: ActorRef) extends Actor {
def receive = {
case id: Long =>
val s = sender
implicit val dispatcher = context.dispatcher
implicit val timeout = 10.seconds
(executor ? AskExecution(id)).mapTo[Option[Long]].pipeTo(s)
}
class Executor extends Actor {
def receive = {
case AskExecution(id) =>
// do something to get a result
val result: Option[Long] = ???
sender ! result
}
}
To test, assuming your test class extends TestKit and mixes in the ImplicitSender trait:
val executor = system.actorOf(Props[Executor])
val theActor = system.actorOf(Props(classOf[TheActor], executor))
within(10.seconds) {
theActor ! 0L
expectMsgClass(classOf[Option[Long]])
}
// test the executor directly
within(10.seconds) {
executor ! AskExecution(3L)
expectMsgClass(classOf[Option[Long]])
}
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.
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
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")