pekko/akka-remote/src/main/scala/akka/remote/RemoteActorRefProvider.scala
Roland 2ce47d6bb5 Bye-bye ReflectiveAccess, introducing PropertyMaster, see #1750
- PropertyMaster is the only place in Akka which calls
  ClassLoader.getClass (apart from kernel, which might be special)
- all PropertyMaster methods (there are only three) take a ClassManifest
  of what is to be constructed, and they verify that the obtained object
  is actually compatible with the required type

Other stuff:
- noticed that I had forgotten to change to ExtendedActorSystem when
  constructing Extensions by ExtensionKey (damn you, reflection!)
- moved Serializer.currentSystem into JavaSerializer, because that’s the
  only one needing it (it’s only used in readResolve() methods)
- Serializers are constructed now with one-arg constructor taking
  ExtendedActorSystem (if that exists, otherwise no-arg as before), to
  allow JavaSerializer to do its magic; possibly necessary for others as
  well
- Removed all Option[ClassLoader] signatures
- made it so that the ActorSystem will try context class loader, then
  the class loader which loaded the class actually calling into
  ActorSystem.apply, then the loader which loaded ActorSystemImpl
- for the second of the above I added a (reflectively accessed hopefully
  safe) facility for getting caller Class[_] objects by using
  sun.reflect.Reflection; this is optional an defaults to None, e.g. on
  Android, which means that getting the caller’s classloader is done on
  a best effort basis (there’s nothing we can do because a StackTrace
  does not contain actual Class[_] objects).
- refactored DurableMailbox to contain the owner val and use that
  instead of declaring that in all subclasses
2012-02-09 11:56:43 +01:00

281 lines
10 KiB
Scala
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.remote
import akka.AkkaException
import akka.actor._
import akka.dispatch._
import akka.event.{ DeathWatch, Logging, LoggingAdapter }
import akka.event.EventStream
import akka.config.ConfigurationException
import java.util.concurrent.{ TimeoutException }
import com.typesafe.config.Config
import akka.serialization.Serialization
import akka.serialization.SerializationExtension
class RemoteException(msg: String) extends AkkaException(msg)
class RemoteCommunicationException(msg: String) extends RemoteException(msg)
class RemoteConnectionException(msg: String) extends RemoteException(msg)
/**
* Remote ActorRefProvider. Starts up actor on remote node and creates a RemoteActorRef representing it.
*/
class RemoteActorRefProvider(
val systemName: String,
val settings: ActorSystem.Settings,
val eventStream: EventStream,
val scheduler: Scheduler,
val propertyMaster: PropertyMaster) extends ActorRefProvider {
val remoteSettings = new RemoteSettings(settings.config, systemName)
val deployer = new RemoteDeployer(settings, propertyMaster)
private val local = new LocalActorRefProvider(systemName, settings, eventStream, scheduler, deployer)
@volatile
private var _log = local.log
def log: LoggingAdapter = _log
def rootPath = local.rootPath
def locker = local.locker
def deadLetters = local.deadLetters
val deathWatch = new RemoteDeathWatch(local.deathWatch, this)
// these are only available after init()
def rootGuardian = local.rootGuardian
def guardian = local.guardian
def systemGuardian = local.systemGuardian
def terminationFuture = local.terminationFuture
def dispatcher = local.dispatcher
def registerTempActor(actorRef: InternalActorRef, path: ActorPath) = local.registerTempActor(actorRef, path)
def unregisterTempActor(path: ActorPath) = local.unregisterTempActor(path)
def tempPath() = local.tempPath()
def tempContainer = local.tempContainer
@volatile
private var _transport: RemoteTransport = _
def transport: RemoteTransport = _transport
@volatile
private var _serialization: Serialization = _
def serialization = _serialization
@volatile
private var _remoteDaemon: InternalActorRef = _
def remoteDaemon = _remoteDaemon
def init(system: ActorSystemImpl) {
local.init(system)
_remoteDaemon = new RemoteSystemDaemon(system, rootPath / "remote", rootGuardian, log)
local.registerExtraNames(Map(("remote", remoteDaemon)))
_serialization = SerializationExtension(system)
_transport = {
val fqn = remoteSettings.RemoteTransport
val args = Seq(
classOf[RemoteSettings] -> remoteSettings,
classOf[ActorSystemImpl] -> system,
classOf[RemoteActorRefProvider] -> this)
system.propertyMaster.getInstanceFor[RemoteTransport](fqn, args) match {
case Left(problem) throw new RemoteTransportException("Could not load remote transport layer " + fqn, problem)
case Right(remote) remote
}
}
_log = Logging(eventStream, "RemoteActorRefProvider(" + transport.address + ")")
// this enables reception of remote requests
_transport.start()
val remoteClientLifeCycleHandler = system.systemActorOf(Props(new Actor {
def receive = {
case RemoteClientError(cause, remote, address) remote.shutdownClientConnection(address)
case RemoteClientDisconnected(remote, address) remote.shutdownClientConnection(address)
case _ //ignore other
}
}), "RemoteClientLifeCycleListener")
system.eventStream.subscribe(remoteClientLifeCycleHandler, classOf[RemoteLifeCycleEvent])
terminationFuture.onComplete(_ transport.shutdown())
}
def actorOf(system: ActorSystemImpl, props: Props, supervisor: InternalActorRef, path: ActorPath,
systemService: Boolean, deploy: Option[Deploy], lookupDeploy: Boolean): InternalActorRef = {
if (systemService) local.actorOf(system, props, supervisor, path, systemService, deploy, lookupDeploy)
else {
/*
* This needs to deal with “mangled” paths, which are created by remote
* deployment, also in this method. The scheme is the following:
*
* Whenever a remote deployment is found, create a path on that remote
* address below “remote”, including the current systems identification
* as “sys@host:port” (typically; it will use whatever the remote
* transport uses). This means that on a path up an actor tree each node
* change introduces one layer or “remote/sys@host:port/” within the URI.
*
* Example:
*
* akka://sys@home:1234/remote/sys@remote:6667/remote/sys@other:3333/user/a/b/c
*
* means that the logical parent originates from “sys@other:3333” with
* one child (may be “a” or “b”) being deployed on “sys@remote:6667” and
* finally either “b” or “c” being created on “sys@home:1234”, where
* this whole thing actually resides. Thus, the logical path is
*/user/a/b/c” and the physical path contains all remote placement
* information.
*
* Deployments are always looked up using the logical path, which is the
* purpose of the lookupRemotes internal method.
*/
@scala.annotation.tailrec
def lookupRemotes(p: Iterable[String]): Option[Deploy] = {
p.headOption match {
case None None
case Some("remote") lookupRemotes(p.drop(2))
case Some("user") deployer.lookup(p.drop(1).mkString("/", "/", ""))
case Some(_) None
}
}
val elems = path.elements
val lookup =
if (lookupDeploy)
elems.head match {
case "user" deployer.lookup(elems.drop(1).mkString("/", "/", ""))
case "remote" lookupRemotes(elems)
case _ None
}
else None
val deployment = {
deploy.toList ::: lookup.toList match {
case Nil Nil
case l List(l reduce ((a, b) b withFallback a))
}
}
Iterator(props.deploy) ++ deployment.iterator reduce ((a, b) b withFallback a) match {
case d @ Deploy(_, _, _, RemoteScope(addr))
if (addr == rootPath.address || addr == transport.address) {
local.actorOf(system, props, supervisor, path, false, deployment.headOption, false)
} else {
val rpath = RootActorPath(addr) / "remote" / transport.address.hostPort / path.elements
useActorOnNode(rpath, props, d, supervisor)
new RemoteActorRef(this, transport, rpath, supervisor)
}
case _ local.actorOf(system, props, supervisor, path, systemService, deployment.headOption, false)
}
}
}
def actorFor(path: ActorPath): InternalActorRef =
if (path.address == rootPath.address || path.address == transport.address) actorFor(rootGuardian, path.elements)
else new RemoteActorRef(this, transport, path, Nobody)
def actorFor(ref: InternalActorRef, path: String): InternalActorRef = path match {
case ActorPathExtractor(address, elems)
if (address == rootPath.address || address == transport.address) actorFor(rootGuardian, elems)
else new RemoteActorRef(this, transport, new RootActorPath(address) / elems, Nobody)
case _ local.actorFor(ref, path)
}
def actorFor(ref: InternalActorRef, path: Iterable[String]): InternalActorRef = local.actorFor(ref, path)
/**
* Using (checking out) actor on a specific node.
*/
def useActorOnNode(path: ActorPath, props: Props, deploy: Deploy, supervisor: ActorRef) {
log.debug("[{}] Instantiating Remote Actor [{}]", rootPath, path)
// we dont wait for the ACK, because the remote end will process this command before any other message to the new actor
actorFor(RootActorPath(path.address) / "remote") ! DaemonMsgCreate(props, deploy, path.toString, supervisor)
}
def getExternalAddressFor(addr: Address): Option[Address] = {
val ta = transport.address
val ra = rootPath.address
addr match {
case `ta` | `ra` Some(rootPath.address)
case Address("akka", _, Some(_), Some(_)) Some(transport.address)
case _ None
}
}
}
trait RemoteRef extends ActorRefScope {
final def isLocal = false
}
/**
* Remote ActorRef that is used when referencing the Actor on a different node than its "home" node.
* This reference is network-aware (remembers its origin) and immutable.
*/
private[akka] class RemoteActorRef private[akka] (
val provider: RemoteActorRefProvider,
remote: RemoteTransport,
val path: ActorPath,
val getParent: InternalActorRef)
extends InternalActorRef with RemoteRef {
def getChild(name: Iterator[String]): InternalActorRef = {
val s = name.toStream
s.headOption match {
case None this
case Some("..") getParent getChild name
case _ new RemoteActorRef(provider, remote, path / s, Nobody)
}
}
@volatile
private var running: Boolean = true
def isTerminated: Boolean = !running
def sendSystemMessage(message: SystemMessage): Unit = remote.send(message, None, this)
override def !(message: Any)(implicit sender: ActorRef = null): Unit = remote.send(message, Option(sender), this)
def suspend(): Unit = sendSystemMessage(Suspend())
def resume(): Unit = sendSystemMessage(Resume())
def stop(): Unit = sendSystemMessage(Terminate())
def restart(cause: Throwable): Unit = sendSystemMessage(Recreate(cause))
@throws(classOf[java.io.ObjectStreamException])
private def writeReplace(): AnyRef = SerializedActorRef(path)
}
class RemoteDeathWatch(val local: LocalDeathWatch, val provider: RemoteActorRefProvider) extends DeathWatch {
def subscribe(watcher: ActorRef, watched: ActorRef): Boolean = watched match {
case r: RemoteRef
val ret = local.subscribe(watcher, watched)
provider.actorFor(r.path.root / "remote") ! DaemonMsgWatch(watcher, watched)
ret
case l: LocalRef
local.subscribe(watcher, watched)
case _
provider.log.error("unknown ActorRef type {} as DeathWatch target", watched.getClass)
false
}
def unsubscribe(watcher: ActorRef, watched: ActorRef): Boolean = local.unsubscribe(watcher, watched)
def unsubscribe(watcher: ActorRef): Unit = local.unsubscribe(watcher)
def publish(event: Terminated): Unit = local.publish(event)
}