Added support for server-initiated remote actors with clients getting a dummy handle to the remote actor

This commit is contained in:
Jonas Bonér 2010-02-16 15:39:09 +01:00
parent 8fb281f4b0
commit 41766bef22
6 changed files with 321 additions and 80 deletions

View file

@ -435,7 +435,7 @@ trait Actor extends TransactionManagement {
_isShutDown = true
shutdown
ActorRegistry.unregister(this)
// _remoteAddress.foreach(address => RemoteClient.unregister(address.getHostName, address.getPort, uuid))
_remoteAddress.foreach(address => RemoteClient.unregister(address.getHostName, address.getPort, uuid))
}
}
@ -483,8 +483,7 @@ trait Actor extends TransactionManagement {
def send(message: Any) = {
if (_isKilled) throw new ActorKilledException("Actor [" + toString + "] has been killed, can't respond to messages")
if (_isRunning) postMessageToMailbox(message, None)
else throw new IllegalStateException(
"Actor has not been started, you need to invoke 'actor.start' before using it")
else throw new IllegalStateException("Actor has not been started, you need to invoke 'actor.start' before using it")
}
/**
@ -784,7 +783,7 @@ trait Actor extends TransactionManagement {
actor
}
private def postMessageToMailbox(message: Any, sender: Option[Actor]): Unit = {
protected[akka] def postMessageToMailbox(message: Any, sender: Option[Actor]): Unit = {
if (_remoteAddress.isDefined) {
val requestBuilder = RemoteRequest.newBuilder
.setId(RemoteRequestIdFactory.nextId)
@ -826,7 +825,7 @@ trait Actor extends TransactionManagement {
}
}
private def postMessageToMailboxAndCreateFutureResultWithTimeout(
protected[akka] def postMessageToMailboxAndCreateFutureResultWithTimeout(
message: Any,
timeout: Long,
senderFuture: Option[CompletableFutureResult]): CompletableFutureResult = {

View file

@ -26,12 +26,62 @@ import java.util.concurrent.atomic.AtomicLong
import scala.collection.mutable.{HashSet, HashMap}
/*
class RemoteActorHandle(id: String, className: String, timeout: Long, hostname: String, port: Int) extends Actor {
start
val remoteClient = RemoteClient.clientFor(hostname, port)
override def postMessageToMailbox(message: Any, sender: Option[Actor]): Unit = {
val requestBuilder = RemoteRequest.newBuilder
.setId(RemoteRequestIdFactory.nextId)
.setTarget(className)
.setTimeout(timeout)
.setUuid(id)
.setIsActor(true)
.setIsOneWay(true)
.setIsEscaped(false)
if (sender.isDefined) {
val s = sender.get
requestBuilder.setSourceTarget(s.getClass.getName)
requestBuilder.setSourceUuid(s.uuid)
val (host, port) = s._replyToAddress.map(a => (a.getHostName, a.getPort)).getOrElse((Actor.HOSTNAME, Actor.PORT))
requestBuilder.setSourceHostname(host)
requestBuilder.setSourcePort(port)
}
RemoteProtocolBuilder.setMessage(message, requestBuilder)
remoteClient.send(requestBuilder.build, None)
}
override def postMessageToMailboxAndCreateFutureResultWithTimeout(
message: Any,
timeout: Long,
senderFuture: Option[CompletableFutureResult]): CompletableFutureResult = {
val requestBuilder = RemoteRequest.newBuilder
.setId(RemoteRequestIdFactory.nextId)
.setTarget(className)
.setTimeout(timeout)
.setUuid(id)
.setIsActor(true)
.setIsOneWay(false)
.setIsEscaped(false)
RemoteProtocolBuilder.setMessage(message, requestBuilder)
val future = remoteClient.send(requestBuilder.build, senderFuture)
if (future.isDefined) future.get
else throw new IllegalStateException("Expected a future from remote call to actor " + toString)
}
def receive = { case _ => {} }
}
*/
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
object RemoteRequestIdFactory {
private val nodeId = UUID.newUuid
private val id = new AtomicLong
def nextId: Long = id.getAndIncrement + nodeId
}
@ -45,6 +95,65 @@ object RemoteClient extends Logging {
private val remoteClients = new HashMap[String, RemoteClient]
private val remoteActors = new HashMap[RemoteServer.Address, HashSet[String]]
// FIXME: simplify overloaded methods when we have Scala 2.8
/*
def actorFor(className: String, hostname: String, port: Int): Actor =
actorFor(className, className, 5000, hostname, port)
def actorFor(actorId: String, className: String, hostname: String, port: Int): Actor =
actorFor(actorId, className, 5000, hostname, port)
*/
def actorFor(className: String, timeout: Long, hostname: String, port: Int): Actor =
actorFor(className, className, timeout, hostname, port)
def actorFor(actorId: String, className: String, timeout: Long, hostname: String, port: Int): Actor = {
new Actor {
start
val remoteClient = RemoteClient.clientFor(hostname, port)
override def postMessageToMailbox(message: Any, sender: Option[Actor]): Unit = {
val requestBuilder = RemoteRequest.newBuilder
.setId(RemoteRequestIdFactory.nextId)
.setTarget(className)
.setTimeout(timeout)
.setUuid(actorId)
.setIsActor(true)
.setIsOneWay(true)
.setIsEscaped(false)
if (sender.isDefined) {
val s = sender.get
requestBuilder.setSourceTarget(s.getClass.getName)
requestBuilder.setSourceUuid(s.uuid)
val (host, port) = s._replyToAddress.map(a => (a.getHostName, a.getPort)).getOrElse((Actor.HOSTNAME, Actor.PORT))
requestBuilder.setSourceHostname(host)
requestBuilder.setSourcePort(port)
}
RemoteProtocolBuilder.setMessage(message, requestBuilder)
remoteClient.send(requestBuilder.build, None)
}
override def postMessageToMailboxAndCreateFutureResultWithTimeout(
message: Any,
timeout: Long,
senderFuture: Option[CompletableFutureResult]): CompletableFutureResult = {
val requestBuilder = RemoteRequest.newBuilder
.setId(RemoteRequestIdFactory.nextId)
.setTarget(className)
.setTimeout(timeout)
.setUuid(actorId)
.setIsActor(true)
.setIsOneWay(false)
.setIsEscaped(false)
RemoteProtocolBuilder.setMessage(message, requestBuilder)
val future = remoteClient.send(requestBuilder.build, senderFuture)
if (future.isDefined) future.get
else throw new IllegalStateException("Expected a future from remote call to actor " + toString)
}
def receive = {case _ => {}}
}
}
def clientFor(hostname: String, port: Int): RemoteClient = clientFor(new InetSocketAddress(hostname, port))
def clientFor(address: InetSocketAddress): RemoteClient = synchronized {

View file

@ -182,13 +182,19 @@ class RemoteServer extends Logging {
}
}
def shutdown = {
def shutdown = if (isRunning) {
RemoteServer.unregister(hostname, port)
openChannels.disconnect
openChannels.close.awaitUninterruptibly
bootstrap.releaseExternalResources
Cluster.deregisterLocalNode(hostname, port)
}
// TODO: register active object in RemoteServer as well
def register(actor: Actor) = if (isRunning) {
log.info("Registering server side remote actor [%s] with id [%s]", actor.getClass.getName, actor.id)
RemoteServer.actorsFor(RemoteServer.Address(hostname, port)).actors.put(actor.id, actor)
}
}
case class Codec(encoder : ChannelHandler, decoder : ChannelHandler)
@ -256,8 +262,7 @@ class RemoteServerHandler(
override def messageReceived(ctx: ChannelHandlerContext, event: MessageEvent) = {
val message = event.getMessage
if (message eq null) throw new IllegalStateException(
"Message in remote MessageEvent is null: " + event)
if (message eq null) throw new IllegalStateException("Message in remote MessageEvent is null: " + event)
if (message.isInstanceOf[RemoteRequest]) {
handleRemoteRequest(message.asInstanceOf[RemoteRequest], event.getChannel)
}

View file

@ -4,7 +4,7 @@ import junit.framework.Test
import junit.framework.TestCase
import junit.framework.TestSuite
import se.scalablesolutions.akka.actor.{RemoteActorTest, InMemoryActorTest, ThreadBasedActorTest, SupervisorTest, RemoteSupervisorTest, SchedulerTest}
import se.scalablesolutions.akka.actor.{ClientInitiatedRemoteActorTest, InMemoryActorTest, ThreadBasedActorTest, SupervisorTest, RemoteSupervisorTest, SchedulerTest}
object AllTest extends TestCase {
def suite(): Test = {
@ -16,7 +16,7 @@ object AllTest extends TestCase {
suite.addTestSuite(classOf[ThreadBasedActorTest])
suite.addTestSuite(classOf[ReactorBasedSingleThreadEventDrivenDispatcherTest])
suite.addTestSuite(classOf[ReactorBasedThreadPoolEventDrivenDispatcherTest])
suite.addTestSuite(classOf[RemoteActorTest])
suite.addTestSuite(classOf[ClientInitiatedRemoteActorTest])
suite.addTestSuite(classOf[InMemoryActorTest])
suite.addTestSuite(classOf[SchedulerTest])
//suite.addTestSuite(classOf[TransactionClasherTest])

View file

@ -9,10 +9,13 @@ import org.junit.{Test, Before, After}
import se.scalablesolutions.akka.remote.{RemoteServer, RemoteClient}
import se.scalablesolutions.akka.dispatch.Dispatchers
object ClientInitiatedRemoteActorTest {
object Global {
var oneWay = "nada"
var remoteReply = "nada"
}
case class Send(actor: Actor)
class RemoteActorSpecActorUnidirectional extends Actor {
dispatcher = Dispatchers.newThreadBasedDispatcher(this)
@ -31,8 +34,6 @@ class RemoteActorSpecActorBidirectional extends Actor {
}
}
case class Send(actor: Actor)
class RemoteActorSpecActorAsyncSender extends Actor {
def receive = {
case Send(actor: Actor) =>
@ -45,10 +46,10 @@ class RemoteActorSpecActorAsyncSender extends Actor {
this ! Send(actor)
}
}
}
class RemoteActorTest extends JUnitSuite {
import Actor.Sender.Self
class ClientInitiatedRemoteActorTest extends JUnitSuite {
import ClientInitiatedRemoteActorTest._
akka.Config.config
val HOSTNAME = "localhost"
@ -57,6 +58,8 @@ class RemoteActorTest extends JUnitSuite {
var s1: RemoteServer = null
var s2: RemoteServer = null
import Actor.Sender.Self
@Before
def init() {
s1 = new RemoteServer()
@ -116,26 +119,7 @@ class RemoteActorTest extends JUnitSuite {
actor.stop
}
/*
This test does not throw an exception since the
_contactAddress is always defined via the
global configuration if not set explicitly.
@Test
def shouldSendRemoteReplyException = {
implicit val timeout = 500000000L
val actor = new RemoteActorSpecActorBidirectional
actor.makeRemote(HOSTNAME, PORT1)
actor.start
val sender = new RemoteActorSpecActorAsyncSender
sender.start
sender.send(actor)
Thread.sleep(500)
assert("exception" === Global.remoteReply)
actor.stop
}
*/
@Test
def shouldSendReceiveException = {
implicit val timeout = 500000000L

View file

@ -0,0 +1,144 @@
package se.scalablesolutions.akka.actor
import java.util.concurrent.TimeUnit
import org.scalatest.junit.JUnitSuite
import org.junit.{Test, Before, After}
import se.scalablesolutions.akka.remote.{RemoteServer, RemoteClient}
import se.scalablesolutions.akka.dispatch.Dispatchers
object ServerInitiatedRemoteActorTest {
val HOSTNAME = "localhost"
val PORT = 9990
var server: RemoteServer = null
object Global {
var oneWay = "nada"
var remoteReply = "nada"
}
class RemoteActorSpecActorUnidirectional extends Actor {
dispatcher = Dispatchers.newThreadBasedDispatcher(this)
start
def receive = {
case "OneWay" =>
println("================== ONEWAY")
Global.oneWay = "received"
}
}
class RemoteActorSpecActorBidirectional extends Actor {
start
def receive = {
case "Hello" =>
reply("World")
case "Failure" =>
throw new RuntimeException("expected")
}
}
case class Send(actor: Actor)
class RemoteActorSpecActorAsyncSender extends Actor {
start
def receive = {
case Send(actor: Actor) =>
actor ! "Hello"
case "World" =>
Global.remoteReply = "replied"
}
def send(actor: Actor) {
this ! Send(actor)
}
}
}
class ServerInitiatedRemoteActorTest extends JUnitSuite {
import ServerInitiatedRemoteActorTest._
import Actor.Sender.Self
akka.Config.config
private val unit = TimeUnit.MILLISECONDS
@Before
def init() {
server = new RemoteServer()
server.start(HOSTNAME, PORT)
server.register(new RemoteActorSpecActorUnidirectional)
server.register(new RemoteActorSpecActorBidirectional)
server.register(new RemoteActorSpecActorAsyncSender)
Thread.sleep(1000)
}
// make sure the servers shutdown cleanly after the test has finished
@After
def finished() {
server.shutdown
RemoteClient.shutdownAll
Thread.sleep(1000)
}
@Test
def shouldSendOneWay = {
val actor = RemoteClient.actorFor(
"se.scalablesolutions.akka.actor.ServerInitiatedRemoteActorTest$RemoteActorSpecActorUnidirectional",
5000L,
HOSTNAME, PORT)
val result = actor ! "OneWay"
Thread.sleep(1000)
assert("received" === Global.oneWay)
actor.stop
}
@Test
def shouldSendReplyAsync = {
val actor = RemoteClient.actorFor(
"se.scalablesolutions.akka.actor.ServerInitiatedRemoteActorTest$RemoteActorSpecActorBidirectional",
5000L,
HOSTNAME, PORT)
val result = actor !! "Hello"
assert("World" === result.get.asInstanceOf[String])
actor.stop
}
@Test
def shouldSendRemoteReply = {
implicit val timeout = 500000000L
val actor = RemoteClient.actorFor(
"se.scalablesolutions.akka.actor.ServerInitiatedRemoteActorTest$RemoteActorSpecActorBidirectional",
timeout,
HOSTNAME, PORT)
val sender = new RemoteActorSpecActorAsyncSender
sender.setReplyToAddress(HOSTNAME, PORT)
sender.start
sender.send(actor)
Thread.sleep(1000)
assert("replied" === Global.remoteReply)
actor.stop
}
@Test
def shouldSendReceiveException = {
implicit val timeout = 500000000L
val actor = RemoteClient.actorFor(
"se.scalablesolutions.akka.actor.ServerInitiatedRemoteActorTest$RemoteActorSpecActorBidirectional",
timeout,
HOSTNAME, PORT)
try {
actor !! "Failure"
fail("Should have thrown an exception")
} catch {
case e =>
assert("expected" === e.getMessage())
}
actor.stop
}
}