From 05058af3705aabfb6e12e50252c70f27ab7a8b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Tue, 18 May 2010 20:25:13 +0200 Subject: [PATCH] Added explicit nullification of all ActorRef references in Actor to make the Actor instance eligable for GC --- LICENSE | 2 +- akka-core/src/main/scala/actor/Actor.scala | 73 ++++++++++++++++--- akka-core/src/main/scala/actor/ActorRef.scala | 34 ++++++++- 3 files changed, 96 insertions(+), 13 deletions(-) diff --git a/LICENSE b/LICENSE index 0acc58cb79..4e1dd06320 100755 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ This software is licensed under the Apache 2 license, quoted below. -Copyright 2009 Scalable Solutions AB [http://scalablesolutions.se] +Copyright 2009-2010 Scalable Solutions AB [http://scalablesolutions.se] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of diff --git a/akka-core/src/main/scala/actor/Actor.scala b/akka-core/src/main/scala/actor/Actor.scala index b66131123d..8b4cb81836 100644 --- a/akka-core/src/main/scala/actor/Actor.scala +++ b/akka-core/src/main/scala/actor/Actor.scala @@ -73,10 +73,11 @@ object Actor extends Logging { val TIMEOUT = config.getInt("akka.actor.timeout", 5000) val SERIALIZE_MESSAGES = config.getBool("akka.actor.serialize-messages", false) - /** A Receive is the type that defines actor message behavior - * currently modeled as a PartialFunction[Any,Unit] + /** + * A Receive is a convenience type that defines actor message behavior currently modeled as + * a PartialFunction[Any, Unit]. */ - type Receive = PartialFunction[Any,Unit] + type Receive = PartialFunction[Any, Unit] private[actor] val actorRefInCreation = new scala.util.DynamicVariable[Option[ActorRef]](None) @@ -89,6 +90,10 @@ object Actor extends Logging { * actor ! message * actor.stop * + * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf[MyActor].start
+   * 
*/ def actorOf[T <: Actor: Manifest]: ActorRef = new LocalActorRef(manifest[T].erasure.asInstanceOf[Class[_ <: Actor]]) @@ -105,6 +110,10 @@ object Actor extends Logging { * actor ! message * actor.stop * + * You can create and start the actor in one statement like this: + *
+   *   val actor = actorOf(new MyActor).start
+   * 
*/ def actorOf(factory: => Actor): ActorRef = new LocalActorRef(() => factory) @@ -239,17 +248,57 @@ object Actor extends Logging { * => SHUT DOWN (when 'exit' is invoked) - can't do anything * * + *

+ * The Actor's API is available in the 'self' member variable. + * + *

+ * Here you find functions like: + * - !, !!, !!! and forward + * - link, unlink, startLink, spawnLink etc + * - makeTransactional, makeRemote etc. + * - start, stop + * - etc. + * + *

+ * Here you also find fields like + * - dispatcher = ... + * - id = ... + * - lifeCycle = ... + * - faultHandler = ... + * - trapExit = ... + * - etc. + * + *

+ * This means that to use them you have to prefix them with 'self', like this: self ! Message + * + * However, for convenience you can import these functions and fields like below, which will allow you do + * drop the 'self' prefix: + *

+ * class MyActor extends Actor {
+ *   import self._
+ *   id = ...
+ *   dispatcher = ...
+ *   spawnLink[OtherActor]
+ *   ...
+ * }
+ * 
+ * + *

+ * The Actor trait also has a 'log' member field that can be used for logging within the Actor. + * * @author Jonas Bonér */ trait Actor extends Logging { - //Type alias because traits cannot have companion objects... + /** + * Type alias because traits cannot have companion objects. + */ type Receive = Actor.Receive - /** + /* * For internal use only, functions as the implicit sender references when invoking - * one of the message send functions (!, !!, !!! and forward). + * one of the message send functions (!, !! and !!!). */ - implicit val optionSelf: Option[ActorRef] = { + implicit val optionSelf: Option[ActorRef] = { val ref = Actor.actorRefInCreation.value Actor.actorRefInCreation.value = None if (ref.isEmpty) throw new ActorInitializationException( @@ -258,10 +307,15 @@ trait Actor extends Logging { "\n\tYou have to use one of the factory methods in the 'Actor' object to create a new actor." + "\n\tEither use:" + "\n\t\t'val actor = Actor.actorOf[MyActor]', or" + - "\n\t\t'val actor = Actor.actorOf(new MyActor(..))'") + "\n\t\t'val actor = Actor.actorOf(new MyActor(..))'" + + "\n\t\t'val actor = Actor.actor { case msg => .. } }'") else ref } + /* + * For internal use only, functions as the implicit sender references when invoking + * the forward function. + */ implicit val someSelf: Some[ActorRef] = optionSelf.asInstanceOf[Some[ActorRef]] /** @@ -274,13 +328,12 @@ trait Actor extends Logging { */ val self: ActorRef = optionSelf.get self.id = getClass.getName - import self._ /** * User overridable callback/setting. *

* Partial function implementing the actor logic. - * To be implemented by subclassing actor. + * To be implemented by concrete actor class. *

* Example code: *

diff --git a/akka-core/src/main/scala/actor/ActorRef.scala b/akka-core/src/main/scala/actor/ActorRef.scala
index db2d0ed030..5a5f08adc7 100644
--- a/akka-core/src/main/scala/actor/ActorRef.scala
+++ b/akka-core/src/main/scala/actor/ActorRef.scala
@@ -26,6 +26,7 @@ import java.util.concurrent.locks.ReentrantLock
 import java.util.concurrent.atomic.AtomicReference
 import java.util.concurrent.ConcurrentHashMap
 import java.util.{Map => JMap}
+import java.lang.reflect.Field
 
 /**
  * The ActorRef object can be used to deserialize ActorRef instances from of its binary representation
@@ -593,7 +594,11 @@ sealed class LocalActorRef private[akka](
 
   @volatile private var isInInitialization = false
   @volatile private var runActorInitialization = false
-  
+
+  // Needed to be able to null out the 'val self: ActorRef' member variables to make the Actor
+  // instance eligeble for garbage collection
+  private val actorSelfFields = findActorSelfField(actor.getClass)
+
   if (runActorInitialization) initializeActorInstance
   
   /**
@@ -720,6 +725,7 @@ sealed class LocalActorRef private[akka](
       remoteAddress.foreach(address => RemoteClient.unregister(
         address.getHostName, address.getPort, uuid))
       RemoteNode.unregister(this)
+      nullOutActorRefReferencesFor(actorInstance.get)
     } else if (isBeingRestarted) throw new ActorKilledException("Actor [" + toString + "] is being restarted.")
   }
 
@@ -1082,6 +1088,7 @@ sealed class LocalActorRef private[akka](
       Actor.log.debug("Restarting linked actors for actor [%s].", id)
       Actor.log.debug("Invoking 'preRestart' for failed actor instance [%s].", id)
       failedActor.preRestart(reason)
+      nullOutActorRefReferencesFor(failedActor)
       val freshActor = newActor
       freshActor.synchronized {
         freshActor.init
@@ -1137,7 +1144,30 @@ sealed class LocalActorRef private[akka](
 
   protected[akka] def linkedActorsAsList: List[ActorRef] = 
     linkedActors.values.toArray.toList.asInstanceOf[List[ActorRef]]
-  
+
+  private def nullOutActorRefReferencesFor(actor: Actor) = {
+    actorSelfFields._1.set(actor, null)
+    actorSelfFields._2.set(actor, null)
+    actorSelfFields._3.set(actor, null)
+  }
+
+  private def findActorSelfField(clazz: Class[_]): Tuple3[Field, Field, Field] = {
+    try {
+      val selfField =       clazz.getDeclaredField("self")
+      val optionSelfField = clazz.getDeclaredField("optionSelf")
+      val someSelfField =   clazz.getDeclaredField("someSelf")
+      selfField.setAccessible(true)
+      optionSelfField.setAccessible(true)
+      someSelfField.setAccessible(true)
+      (selfField, optionSelfField, someSelfField)
+    } catch {
+      case e: NoSuchFieldException =>
+        val parent = clazz.getSuperclass
+        if (parent != null) findActorSelfField(parent)
+        else throw new IllegalStateException(toString + " is not an Actor since it have not mixed in the 'Actor' trait")
+    }
+  }
+
   private def initializeActorInstance = if (!isRunning) {
     dispatcher.register(this)
     dispatcher.start