From 34dee73c714ef37c8291cc53fcd2633437e309b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Thu, 8 Apr 2010 17:03:48 +0200 Subject: [PATCH 01/27] removed Actor.remoteActor factory method since it does not work --- akka-core/src/main/scala/actor/Actor.scala | 23 ---------------------- 1 file changed, 23 deletions(-) diff --git a/akka-core/src/main/scala/actor/Actor.scala b/akka-core/src/main/scala/actor/Actor.scala index 113fab5f32..2aea63bc4c 100644 --- a/akka-core/src/main/scala/actor/Actor.scala +++ b/akka-core/src/main/scala/actor/Actor.scala @@ -151,29 +151,6 @@ object Actor extends Logging { def receive = body } - /** - * Use to create an anonymous event-driven remote actor. - *

- * The actor is created with a 'permanent' life-cycle configuration, which means that - * if the actor is supervised and dies it will be restarted. - *

- * The actor is started when created. - * Example: - *

-   * import Actor._
-   *
-   * val a = remoteActor("localhost", 9999) {
-   *   case msg => ... // handle message
-   * }
-   * 
- */ - def remoteActor(hostname: String, port: Int)(body: PartialFunction[Any, Unit]): Actor = new Actor() { - lifeCycle = Some(LifeCycle(Permanent)) - makeRemote(hostname, port) - start - def receive = body - } - /** * Use to create an anonymous event-driven actor with both an init block and a message loop block. *

From 2b8db325874ba975bf46b42dd3ffcf6d5f1ae1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Thu, 8 Apr 2010 17:05:59 +0200 Subject: [PATCH 02/27] improved scaladoc for Actor.scala --- akka-core/src/main/scala/actor/Actor.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/akka-core/src/main/scala/actor/Actor.scala b/akka-core/src/main/scala/actor/Actor.scala index 2aea63bc4c..4bc3a9dc31 100644 --- a/akka-core/src/main/scala/actor/Actor.scala +++ b/akka-core/src/main/scala/actor/Actor.scala @@ -520,6 +520,7 @@ trait Actor extends TransactionManagement with Logging { /** * Sends a message asynchronously and waits on a future for a reply message. + * Uses the time-out defined in the Actor. *

* It waits on the reply either until it receives it (in the form of Some(replyMessage)) * or until the timeout expires (which will return None). E.g. send-and-receive-eventually semantics. From 08695c55dcd4049edafcfb1bf14bc16642564553 Mon Sep 17 00:00:00 2001 From: Jan Kronquist Date: Thu, 8 Apr 2010 20:52:22 +0200 Subject: [PATCH 03/27] Started working on issue #121 Added actorFor to get the actor for an activeObject --- .../src/main/scala/actor/ActiveObject.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/akka-core/src/main/scala/actor/ActiveObject.scala b/akka-core/src/main/scala/actor/ActiveObject.scala index a5a81fe40c..e3bd9ef943 100644 --- a/akka-core/src/main/scala/actor/ActiveObject.scala +++ b/akka-core/src/main/scala/actor/ActiveObject.scala @@ -199,6 +199,18 @@ object ActiveObject { proxy.asInstanceOf[T] } +// Jan Kronquist: started work on issue 121 +// def actorFor(obj: AnyRef): Option[Actor] = { +// ActorRegistry.actorsFor(classOf[Dispatcher]).find(a=>a.target == Some(obj)) +// } +// +// def link(supervisor: AnyRef, activeObject: AnyRef) = { +// actorFor(supervisor).get !! Link(actorFor(activeObject).get) +// } +// +// def unlink(supervisor: AnyRef, activeObject: AnyRef) = { +// actorFor(supervisor).get !! Unlink(actorFor(activeObject).get) +// } private[akka] def supervise(restartStrategy: RestartStrategy, components: List[Supervise]): Supervisor = { val factory = SupervisorFactory(SupervisorConfig(restartStrategy, components)) @@ -353,6 +365,9 @@ private[akka] sealed class ActiveObjectAspect { } } +// Jan Kronquist: started work on issue 121 +// private[akka] case class Link(val actor: Actor) + object Dispatcher { val ZERO_ITEM_CLASS_ARRAY = Array[Class[_]]() val ZERO_ITEM_OBJECT_ARRAY = Array[Object]() @@ -418,6 +433,9 @@ private[akka] class Dispatcher(transactionalRequired: Boolean, val callbacks: Op if (Actor.SERIALIZE_MESSAGES) serializeArguments(joinPoint) if (isOneWay) joinPoint.proceed else reply(joinPoint.proceed) +// Jan Kronquist: started work on issue 121 +// case Link(target) => +// link(target) case unexpected => throw new IllegalStateException("Unexpected message [" + unexpected + "] sent to [" + this + "]") } From bdd7b9e637a64124a8e90e681d9e6396a18093d5 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Fri, 9 Apr 2010 05:00:09 +0800 Subject: [PATCH 04/27] fix for HashTrie: apply and + now return HashTrie rather than Map --- akka-core/src/main/scala/stm/HashTrie.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/akka-core/src/main/scala/stm/HashTrie.scala b/akka-core/src/main/scala/stm/HashTrie.scala index 8ef4138d85..91930390c5 100644 --- a/akka-core/src/main/scala/stm/HashTrie.scala +++ b/akka-core/src/main/scala/stm/HashTrie.scala @@ -30,7 +30,7 @@ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **/ + */ package se.scalablesolutions.akka.stm @@ -52,9 +52,7 @@ final class HashTrie[K, +V] private (root: Node[K, V]) extends Map[K, V] with Pe def get(key: K) = root(key, key.hashCode) - override def +[A >: V](pair: (K, A)) = pair match { - case (k, v) => update(k, v) - } + override def +[A >: V](pair: (K, A)) = update(pair._1, pair._2) override def update[A >: V](key: K, value: A) = new HashTrie(root(0, key, key.hashCode) = value) @@ -68,7 +66,7 @@ final class HashTrie[K, +V] private (root: Node[K, V]) extends Map[K, V] with Pe } object HashTrie { - def apply[K, V](pairs: (K, V)*) = pairs.foldLeft((new HashTrie[K, V]).asInstanceOf[Map[K,V]]) { _ + _ } + def apply[K, V](pairs: (K, V)*) = pairs.foldLeft(new HashTrie[K, V]) { _ + _ } def unapplySeq[K, V](map: HashTrie[K, V]) = map.toSeq } From 3f7c1fe24098b4d08aae4dc9363b8d639d836805 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Fri, 9 Apr 2010 05:17:36 +0800 Subject: [PATCH 05/27] Added alter method to TransactionalRef --- akka-core/src/main/scala/stm/TransactionalState.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/akka-core/src/main/scala/stm/TransactionalState.scala b/akka-core/src/main/scala/stm/TransactionalState.scala index 7afb3fb6bb..4949ce389d 100644 --- a/akka-core/src/main/scala/stm/TransactionalState.scala +++ b/akka-core/src/main/scala/stm/TransactionalState.scala @@ -102,6 +102,13 @@ class TransactionalRef[T] extends Transactional { ref.set(elem) } + def alter(f: T => T): T = { + ensureIsInTransaction + ensureNotNull + ref.set(f(ref.get)) + ref.get + } + def get: Option[T] = { ensureIsInTransaction if (ref.isNull) None @@ -171,6 +178,9 @@ class TransactionalRef[T] extends Transactional { private def ensureIsInTransaction = if (getThreadLocalTransaction eq null) throw new NoTransactionInScopeException + + private def ensureNotNull = + if (ref.isNull) throw new RuntimeException("Cannot alter Ref's value when it is null") } object TransactionalMap { From 4604d22bf5a1ddc055e15ad721a780440221b461 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Fri, 9 Apr 2010 05:32:58 +0800 Subject: [PATCH 06/27] Initial values possible for TransactionalRef, TransactionalMap, and TransactionalVector --- .../main/scala/stm/TransactionalState.scala | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/akka-core/src/main/scala/stm/TransactionalState.scala b/akka-core/src/main/scala/stm/TransactionalState.scala index 4949ce389d..e84beaa4f0 100644 --- a/akka-core/src/main/scala/stm/TransactionalState.scala +++ b/akka-core/src/main/scala/stm/TransactionalState.scala @@ -32,8 +32,13 @@ import org.multiverse.stms.alpha.AlphaRef */ object TransactionalState { def newMap[K, V] = TransactionalMap[K, V]() + def newMap[K, V](pairs: (K, V)*) = TransactionalMap(pairs: _*) + def newVector[T] = TransactionalVector[T]() + def newVector[T](elems: T*) = TransactionalVector(elems :_*) + def newRef[T] = TransactionalRef[T]() + def newRef[T](initialValue: T) = TransactionalRef(initialValue) } /** @@ -58,6 +63,8 @@ trait Committable { */ object Ref { def apply[T]() = new Ref[T] + + def apply[T](initialValue: T) = new Ref[T](Some(initialValue)) } /** @@ -73,6 +80,8 @@ object TransactionalRef { implicit def ref2Iterable[T](ref: TransactionalRef[T]): Iterable[T] = ref.toList def apply[T]() = new TransactionalRef[T] + + def apply[T](initialValue: T) = new TransactionalRef[T](Some(initialValue)) } /** @@ -81,7 +90,7 @@ object TransactionalRef { * * @author Jonas Bonér */ -class Ref[T] extends TransactionalRef[T] +class Ref[T](initialOpt: Option[T] = None) extends TransactionalRef[T](initialOpt) /** * Implements a transactional managed reference. @@ -89,13 +98,17 @@ class Ref[T] extends TransactionalRef[T] * * @author Jonas Bonér */ -class TransactionalRef[T] extends Transactional { +class TransactionalRef[T](initialOpt: Option[T] = None) extends Transactional { import org.multiverse.api.ThreadLocalTransaction._ implicit val txInitName = "TransactionalRef:Init" val uuid = UUID.newUuid.toString - private[this] lazy val ref: AlphaRef[T] = new AlphaRef + private[this] lazy val ref = { + val r = new AlphaRef[T] + initialOpt.foreach(r.set(_)) + r + } def swap(elem: T) = { ensureIsInTransaction @@ -185,6 +198,8 @@ class TransactionalRef[T] extends Transactional { object TransactionalMap { def apply[K, V]() = new TransactionalMap[K, V] + + def apply[K, V](pairs: (K, V)*) = new TransactionalMap(Some(HashTrie(pairs: _*))) } /** @@ -194,11 +209,10 @@ object TransactionalMap { * * @author Jonas Bonér */ -class TransactionalMap[K, V] extends Transactional with scala.collection.mutable.Map[K, V] { - protected[this] val ref = TransactionalRef[HashTrie[K, V]] +class TransactionalMap[K, V](initialOpt: Option[HashTrie[K, V]] = None) extends Transactional with scala.collection.mutable.Map[K, V] { val uuid = UUID.newUuid.toString - ref.swap(new HashTrie[K, V]) + protected[this] lazy val ref = new TransactionalRef(initialOpt.orElse(Some(new HashTrie[K, V]))) def -=(key: K) = { remove(key) @@ -249,10 +263,17 @@ class TransactionalMap[K, V] extends Transactional with scala.collection.mutable override def equals(other: Any): Boolean = other.isInstanceOf[TransactionalMap[_, _]] && other.hashCode == hashCode + + override def toString = if (outsideTransaction) "" else super.toString + + def outsideTransaction = + org.multiverse.api.ThreadLocalTransaction.getThreadLocalTransaction eq null } object TransactionalVector { def apply[T]() = new TransactionalVector[T] + + def apply[T](elems: T*) = new TransactionalVector(Some(Vector(elems: _*))) } /** @@ -262,12 +283,10 @@ object TransactionalVector { * * @author Jonas Bonér */ -class TransactionalVector[T] extends Transactional with IndexedSeq[T] { +class TransactionalVector[T](initialOpt: Option[Vector[T]] = None) extends Transactional with IndexedSeq[T] { val uuid = UUID.newUuid.toString - private[this] val ref = TransactionalRef[Vector[T]] - - ref.swap(EmptyVector) + private[this] lazy val ref = new TransactionalRef(initialOpt.orElse(Some(EmptyVector))) def clear = ref.swap(EmptyVector) @@ -293,5 +312,10 @@ class TransactionalVector[T] extends Transactional with IndexedSeq[T] { override def equals(other: Any): Boolean = other.isInstanceOf[TransactionalVector[_]] && other.hashCode == hashCode + + override def toString = if (outsideTransaction) "" else super.toString + + def outsideTransaction = + org.multiverse.api.ThreadLocalTransaction.getThreadLocalTransaction eq null } From 988f16c5a6f2522e09f3c4833eb3dcd1c4603e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Fri, 9 Apr 2010 11:16:38 +0200 Subject: [PATCH 07/27] fixed bug in Agent.scala, fixed bug in RemoteClient.scala, fixed problem with tests --- akka-core/src/main/scala/actor/Agent.scala | 8 +- ...sedEventDrivenWorkStealingDispatcher.scala | 2 +- .../src/main/scala/remote/RemoteClient.scala | 16 +-- akka-core/src/test/scala/AgentSpec.scala | 134 ++++++++---------- ...ventDrivenWorkStealingDispatcherSpec.scala | 11 +- project/build/AkkaProject.scala | 2 +- 6 files changed, 80 insertions(+), 93 deletions(-) diff --git a/akka-core/src/main/scala/actor/Agent.scala b/akka-core/src/main/scala/actor/Agent.scala index a0ca0c90eb..b6a65423a3 100644 --- a/akka-core/src/main/scala/actor/Agent.scala +++ b/akka-core/src/main/scala/actor/Agent.scala @@ -90,7 +90,7 @@ class AgentException private[akka](message: String) extends RuntimeException(mes * * IMPORTANT: * You can *not* call 'agent.get', 'agent()' or use the monadic 'foreach', -* 'map and 'flatMap' within an enclosing transaction since that would block +* 'map' and 'flatMap' within an enclosing transaction since that would block * the transaction indefinitely. But all other operations are fine. The system * will raise an error (e.g. *not* deadlock) if you try to do so, so as long as * you test your application thoroughly you should be fine. @@ -99,11 +99,13 @@ class AgentException private[akka](message: String) extends RuntimeException(mes * @author Jonas Bonér */ sealed class Agent[T] private (initialValue: T) extends Transactor { + start import Agent._ + log.debug("Starting up Agent [%s]", _uuid) + private lazy val value = Ref[T]() - start - this !! Value(initialValue) + this ! Value(initialValue) /** * Periodically handles incoming messages. diff --git a/akka-core/src/main/scala/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcher.scala b/akka-core/src/main/scala/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcher.scala index a769f92e55..a96f5c5e76 100644 --- a/akka-core/src/main/scala/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcher.scala +++ b/akka-core/src/main/scala/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcher.scala @@ -168,7 +168,7 @@ class ExecutorBasedEventDrivenWorkStealingDispatcher(_name: String) extends Mess private def donateMessage(receiver: Actor, thief: Actor): Option[MessageInvocation] = { val donated = receiver._mailbox.pollLast if (donated != null) { - thief.forward(donated.message)(Some(donated.receiver)) + thief ! donated.message return Some(donated) } else return None } diff --git a/akka-core/src/main/scala/remote/RemoteClient.scala b/akka-core/src/main/scala/remote/RemoteClient.scala index 72a7f37229..81d5591fbb 100644 --- a/akka-core/src/main/scala/remote/RemoteClient.scala +++ b/akka-core/src/main/scala/remote/RemoteClient.scala @@ -200,7 +200,7 @@ class RemoteClient(val hostname: String, val port: Int) extends Logging { val channel = connection.awaitUninterruptibly.getChannel openChannels.add(channel) if (!connection.isSuccess) { - listeners.toArray.asInstanceOf[Array[Actor]].foreach(_ ! RemoteClientError(connection.getCause)) + listeners.toArray.foreach(l => l.asInstanceOf[Actor] ! RemoteClientError(connection.getCause)) log.error(connection.getCause, "Remote client connection to [%s:%s] has failed", hostname, port) } isRunning = true @@ -232,7 +232,7 @@ class RemoteClient(val hostname: String, val port: Int) extends Logging { } } else { val exception = new IllegalStateException("Remote client is not running, make sure you have invoked 'RemoteClient.connect' before using it.") - listeners.toArray.asInstanceOf[Array[Actor]].foreach(_ ! RemoteClientError(exception)) + listeners.toArray.foreach(l => l.asInstanceOf[Actor] ! RemoteClientError(exception)) throw exception } @@ -325,12 +325,12 @@ class RemoteClientHandler(val name: String, futures.remove(reply.getId) } else { val exception = new IllegalArgumentException("Unknown message received in remote client handler: " + result) - client.listeners.toArray.asInstanceOf[Array[Actor]].foreach(_ ! RemoteClientError(exception)) + client.listeners.toArray.foreach(l => l.asInstanceOf[Actor] ! RemoteClientError(exception)) throw exception } } catch { case e: Exception => - client.listeners.toArray.asInstanceOf[Array[Actor]].foreach(_ ! RemoteClientError(e)) + client.listeners.toArray.foreach(l => l.asInstanceOf[Actor] ! RemoteClientError(e)) log.error("Unexpected exception in remote client handler: %s", e) throw e } @@ -345,7 +345,7 @@ class RemoteClientHandler(val name: String, // Wait until the connection attempt succeeds or fails. client.connection.awaitUninterruptibly if (!client.connection.isSuccess) { - client.listeners.toArray.asInstanceOf[Array[Actor]].foreach(_ ! RemoteClientError(client.connection.getCause)) + client.listeners.toArray.foreach(l => l.asInstanceOf[Actor] ! RemoteClientError(client.connection.getCause)) log.error(client.connection.getCause, "Reconnection to [%s] has failed", remoteAddress) } } @@ -353,17 +353,17 @@ class RemoteClientHandler(val name: String, } override def channelConnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { - client.listeners.toArray.asInstanceOf[Array[Actor]].foreach(_ ! RemoteClientConnected(client.hostname, client.port)) + client.listeners.toArray.foreach(l => l.asInstanceOf[Actor] ! RemoteClientConnected(client.hostname, client.port)) log.debug("Remote client connected to [%s]", ctx.getChannel.getRemoteAddress) } override def channelDisconnected(ctx: ChannelHandlerContext, event: ChannelStateEvent) = { - client.listeners.toArray.asInstanceOf[Array[Actor]].foreach(_ ! RemoteClientDisconnected(client.hostname, client.port)) + client.listeners.toArray.foreach(l => l.asInstanceOf[Actor] ! RemoteClientDisconnected(client.hostname, client.port)) log.debug("Remote client disconnected from [%s]", ctx.getChannel.getRemoteAddress) } override def exceptionCaught(ctx: ChannelHandlerContext, event: ExceptionEvent) = { - client.listeners.toArray.asInstanceOf[Array[Actor]].foreach(_ ! RemoteClientError(event.getCause)) + client.listeners.toArray.foreach(l => l.asInstanceOf[Actor] ! RemoteClientError(event.getCause)) log.error(event.getCause, "Unexpected exception from downstream in remote client") event.getChannel.close } diff --git a/akka-core/src/test/scala/AgentSpec.scala b/akka-core/src/test/scala/AgentSpec.scala index 20f8e0d8f4..d38f6a4265 100644 --- a/akka-core/src/test/scala/AgentSpec.scala +++ b/akka-core/src/test/scala/AgentSpec.scala @@ -1,6 +1,5 @@ package se.scalablesolutions.akka.actor -import _root_.java.util.concurrent.TimeUnit import se.scalablesolutions.akka.actor.Actor.transactor import se.scalablesolutions.akka.stm.Transaction.Global.atomic import se.scalablesolutions.akka.util.Logging @@ -10,51 +9,40 @@ import org.scalatest.junit.JUnitRunner import org.scalatest.matchers.MustMatchers import org.junit.runner.RunWith -import org.junit.{Test} +import org.junit.Test -import java.util.concurrent.CountDownLatch +import java.util.concurrent.{TimeUnit, CountDownLatch} @RunWith(classOf[JUnitRunner]) -class AgentSpec extends junit.framework.TestCase -with Suite with MustMatchers -with ActorTestUtil with Logging { +class AgentSpec extends junit.framework.TestCase with Suite with MustMatchers { - @Test def testSendFun = verify(new TestActor { - def test = { - val agent = Agent(5) - handle(agent) { - agent send (_ + 1) - agent send (_ * 2) - val result = agent() - result must be(12) - } - } - }) + @Test def testSendFun = { + val agent = Agent(5) + agent send (_ + 1) + agent send (_ * 2) + val result = agent() + result must be(12) + agent.stop + } - @Test def testSendValue = verify(new TestActor { - def test = { - val agent = Agent(5) - handle(agent) { - agent send 6 - val result = agent() - result must be(6) - } - } - }) + @Test def testSendValue = { + val agent = Agent(5) + agent send 6 + val result = agent() + result must be(6) + agent.stop + } - @Test def testSendProc = verify(new TestActor { - def test = { - val agent = Agent(5) - var result = 0 - val latch = new CountDownLatch(2) - handle(agent) { - agent sendProc { e => result += e; latch.countDown } - agent sendProc { e => result += e; latch.countDown } - assert(latch.await(1, TimeUnit.SECONDS)) - result must be(10) - } - } - }) + @Test def testSendProc = { + val agent = Agent(5) + var result = 0 + val latch = new CountDownLatch(2) + agent sendProc { e => result += e; latch.countDown } + agent sendProc { e => result += e; latch.countDown } + assert(latch.await(5, TimeUnit.SECONDS)) + result must be(10) + agent.stop + } @Test def testOneAgentsendWithinEnlosingTransactionSuccess = { case object Go @@ -64,7 +52,7 @@ with ActorTestUtil with Logging { case Go => agent send { e => latch.countDown; e + 1 } } tx ! Go - assert(latch.await(1, TimeUnit.SECONDS)) + assert(latch.await(5, TimeUnit.SECONDS)) val result = agent() result must be(6) agent.close @@ -84,46 +72,40 @@ with ActorTestUtil with Logging { } } tx ! Go - assert(latch.await(1, TimeUnit.SECONDS)) + assert(latch.await(5, TimeUnit.SECONDS)) agent.close tx.stop assert(true) } - @Test def testAgentForeach = verify(new TestActor { - def test = { - val agent1 = Agent(3) - var result = 0 - for (first <- agent1) { - result = first + 1 - } - result must be(4) - agent1.close + @Test def testAgentForeach = { + val agent1 = Agent(3) + var result = 0 + for (first <- agent1) { + result = first + 1 } - }) + result must be(4) + agent1.close + } + + @Test def testAgentMap = { + val agent1 = Agent(3) + val result = for (first <- agent1) yield first + 1 + result() must be(4) + result.close + agent1.close + } - @Test def testAgentMap = verify(new TestActor { - def test = { - val agent1 = Agent(3) - val result = for (first <- agent1) yield first + 1 - result() must be(4) - result.close - agent1.close - } - }) - - @Test def testAgentFlatMap = verify(new TestActor { - def test = { - val agent1 = Agent(3) - val agent2 = Agent(5) - val result = for { - first <- agent1 - second <- agent2 - } yield second + first - result() must be(8) - result.close - agent1.close - agent2.close - } - }) + @Test def testAgentFlatMap = { + val agent1 = Agent(3) + val agent2 = Agent(5) + val result = for { + first <- agent1 + second <- agent2 + } yield second + first + result() must be(8) + result.close + agent1.close + agent2.close + } } diff --git a/akka-core/src/test/scala/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala b/akka-core/src/test/scala/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala index 231e4f4d98..2a194cc454 100644 --- a/akka-core/src/test/scala/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala +++ b/akka-core/src/test/scala/ExecutorBasedEventDrivenWorkStealingDispatcherSpec.scala @@ -1,11 +1,14 @@ package se.scalablesolutions.akka.actor -import org.scalatest.junit.JUnitSuite -import org.junit.Test -import java.util.concurrent.CountDownLatch import org.scalatest.matchers.MustMatchers +import org.scalatest.junit.JUnitSuite + +import org.junit.Test + import se.scalablesolutions.akka.dispatch.Dispatchers +import java.util.concurrent.{TimeUnit, CountDownLatch} + /** * @author Jan Van Besien */ @@ -51,7 +54,7 @@ class ExecutorBasedEventDrivenWorkStealingDispatcherSpec extends JUnitSuite with slow ! i } - finishedCounter.await + finishedCounter.await(5, TimeUnit.SECONDS) fast.invocationCount must be > (slow.invocationCount) } } diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index b7c02135fc..b5c776fb80 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -59,7 +59,7 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { Path.fromFile(home) } val encodingUtf8 = List("-encoding", "UTF-8") - override def parallelExecution = true + override def parallelExecution = false lazy val deployPath = akkaHome / "deploy" lazy val distPath = akkaHome / "dist" From 10b1c6f13490e2afc80c13ec4f4ffb8f481532e9 Mon Sep 17 00:00:00 2001 From: Michael Kober Date: Fri, 9 Apr 2010 13:27:22 +0200 Subject: [PATCH 08/27] added test for supervised remote active object --- akka-spring/akka-spring-test-java/pom.xml | 8 +- .../spring/SupervisorConfigurationTest.java | 150 ++++++++++++------ 2 files changed, 108 insertions(+), 50 deletions(-) diff --git a/akka-spring/akka-spring-test-java/pom.xml b/akka-spring/akka-spring-test-java/pom.xml index e3cd95d74f..0d35a47739 100644 --- a/akka-spring/akka-spring-test-java/pom.xml +++ b/akka-spring/akka-spring-test-java/pom.xml @@ -147,22 +147,22 @@ se.scalablesolutions.akka akka-core_2.8.0.Beta1 - 0.8 + 0.8.1 se.scalablesolutions.akka akka-util_2.8.0.Beta1 - 0.8 + 0.8.1 se.scalablesolutions.akka akka-util-java_2.8.0.Beta1 - 0.8 + 0.8.1 se.scalablesolutions.akka akka-spring_2.8.0.Beta1 - 0.8 + 0.8.1 org.springframework diff --git a/akka-spring/akka-spring-test-java/src/test/java/se/scalablesolutions/akka/spring/SupervisorConfigurationTest.java b/akka-spring/akka-spring-test-java/src/test/java/se/scalablesolutions/akka/spring/SupervisorConfigurationTest.java index ace0bd285a..fbbe23d18c 100644 --- a/akka-spring/akka-spring-test-java/src/test/java/se/scalablesolutions/akka/spring/SupervisorConfigurationTest.java +++ b/akka-spring/akka-spring-test-java/src/test/java/se/scalablesolutions/akka/spring/SupervisorConfigurationTest.java @@ -6,6 +6,7 @@ package se.scalablesolutions.akka.spring; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import net.lag.configgy.Config; import org.junit.Before; import org.junit.Test; @@ -14,64 +15,121 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import se.scalablesolutions.akka.actor.ActiveObject; import se.scalablesolutions.akka.config.ActiveObjectConfigurator; +import se.scalablesolutions.akka.config.JavaConfig.AllForOne; +import se.scalablesolutions.akka.config.JavaConfig.Component; +import se.scalablesolutions.akka.config.JavaConfig.LifeCycle; +import se.scalablesolutions.akka.config.JavaConfig.Permanent; +import se.scalablesolutions.akka.config.JavaConfig.RemoteAddress; +import se.scalablesolutions.akka.config.JavaConfig.RestartStrategy; +import se.scalablesolutions.akka.remote.RemoteNode; import se.scalablesolutions.akka.spring.foo.Foo; import se.scalablesolutions.akka.spring.foo.IBar; import se.scalablesolutions.akka.spring.foo.MyPojo; import se.scalablesolutions.akka.spring.foo.StatefulPojo; - /** * Testclass for supervisor configuration. + * * @author michaelkober - * + * */ public class SupervisorConfigurationTest { - - private ApplicationContext context = null; - @Before - public void setUp() { - context = new ClassPathXmlApplicationContext("se/scalablesolutions/akka/spring/foo/supervisor-config.xml"); - } - - @Test - public void testSupervision() { - // get ActiveObjectConfigurator bean from spring context - ActiveObjectConfigurator myConfigurator = (ActiveObjectConfigurator) context.getBean("supervision1"); - // get ActiveObjects - Foo foo = myConfigurator.getInstance(Foo.class); - assertNotNull(foo); - IBar bar = myConfigurator.getInstance(IBar.class); - assertNotNull(bar); - MyPojo pojo = myConfigurator.getInstance(MyPojo.class); - assertNotNull(pojo); - } + private ApplicationContext context = null; - @Test - public void testTransactionalState() { - ActiveObjectConfigurator conf = (ActiveObjectConfigurator) context.getBean("supervision2"); - StatefulPojo stateful = conf.getInstance(StatefulPojo.class); - stateful.setMapState("testTransactionalState", "some map state"); - stateful.setVectorState("some vector state"); - stateful.setRefState("some ref state"); - assertEquals("some map state", stateful.getMapState("testTransactionalState")); - assertEquals("some vector state", stateful.getVectorState()); - assertEquals("some ref state", stateful.getRefState()); - } - - @Test - public void testInitTransactionalState() { - StatefulPojo stateful = ActiveObject.newInstance(StatefulPojo.class, 1000, true); - assertTrue("should be inititalized", stateful.isInitialized()); - } + @Before + public void setUp() { + context = new ClassPathXmlApplicationContext( + "se/scalablesolutions/akka/spring/foo/supervisor-config.xml"); + } - @Test - public void testSupervisionWithDispatcher() { - ActiveObjectConfigurator myConfigurator = (ActiveObjectConfigurator) context.getBean("supervision-with-dispatcher"); - // get ActiveObjects - Foo foo = myConfigurator.getInstance(Foo.class); - assertNotNull(foo); - // TODO how to check dispatcher? - } + @Test + public void testSupervision() { + // get ActiveObjectConfigurator bean from spring context + ActiveObjectConfigurator myConfigurator = (ActiveObjectConfigurator) context + .getBean("supervision1"); + // get ActiveObjects + Foo foo = myConfigurator.getInstance(Foo.class); + assertNotNull(foo); + IBar bar = myConfigurator.getInstance(IBar.class); + assertNotNull(bar); + MyPojo pojo = myConfigurator.getInstance(MyPojo.class); + assertNotNull(pojo); + } + @Test + public void testTransactionalState() { + ActiveObjectConfigurator conf = (ActiveObjectConfigurator) context + .getBean("supervision2"); + StatefulPojo stateful = conf.getInstance(StatefulPojo.class); + stateful.setMapState("testTransactionalState", "some map state"); + stateful.setVectorState("some vector state"); + stateful.setRefState("some ref state"); + assertEquals("some map state", stateful + .getMapState("testTransactionalState")); + assertEquals("some vector state", stateful.getVectorState()); + assertEquals("some ref state", stateful.getRefState()); + } + + @Test + public void testInitTransactionalState() { + StatefulPojo stateful = ActiveObject.newInstance(StatefulPojo.class, + 1000, true); + assertTrue("should be inititalized", stateful.isInitialized()); + } + + @Test + public void testSupervisionWithDispatcher() { + ActiveObjectConfigurator myConfigurator = (ActiveObjectConfigurator) context + .getBean("supervision-with-dispatcher"); + // get ActiveObjects + Foo foo = myConfigurator.getInstance(Foo.class); + assertNotNull(foo); + // TODO how to check dispatcher? + } + + @Test + public void testRemoteActiveObject() { + new Thread(new Runnable() { + public void run() { + RemoteNode.start(); + } + }).start(); + try { + Thread.currentThread().sleep(1000); + } catch (Exception e) { + } + Foo instance = ActiveObject.newRemoteInstance(Foo.class, 2000, "localhost", 9999); + System.out.println(instance.foo()); + } + + + @Test + public void testSupervisedRemoteActiveObject() { + new Thread(new Runnable() { + public void run() { + RemoteNode.start(); + } + }).start(); + try { + Thread.currentThread().sleep(1000); + } catch (Exception e) { + } + + ActiveObjectConfigurator conf = new ActiveObjectConfigurator(); + conf.configure( + new RestartStrategy(new AllForOne(), 3, 10000, new Class[] { Exception.class }), + new Component[] { + new Component( + Foo.class, + new LifeCycle(new Permanent()), + 10000, + new RemoteAddress("localhost", 9999)) + }).supervise(); + + Foo instance = conf.getInstance(Foo.class); + assertEquals("foo", instance.foo()); + } + + } From ce732295fa8a6c190d54da4392ce2ed689f4b3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Fri, 9 Apr 2010 14:24:33 +0200 Subject: [PATCH 09/27] cleaned up remote tests + remvod akkaHome from sbt build file --- .../ClientInitiatedRemoteActorSpec.scala | 55 +++++-------------- .../ServerInitiatedRemoteActorSpec.scala | 24 ++++---- project/build/AkkaProject.scala | 55 +++---------------- 3 files changed, 33 insertions(+), 101 deletions(-) diff --git a/akka-core/src/test/scala/ClientInitiatedRemoteActorSpec.scala b/akka-core/src/test/scala/ClientInitiatedRemoteActorSpec.scala index 71d8c0b0e2..718c8b88c1 100644 --- a/akka-core/src/test/scala/ClientInitiatedRemoteActorSpec.scala +++ b/akka-core/src/test/scala/ClientInitiatedRemoteActorSpec.scala @@ -9,16 +9,17 @@ import org.junit.{Test, Before, After} import se.scalablesolutions.akka.remote.{RemoteServer, RemoteClient} import se.scalablesolutions.akka.dispatch.Dispatchers -object Global { - val oneWay = new CountDownLatch(1) - val remoteReply = new CountDownLatch(1) +case class Send(actor: Actor) + +object RemoteActorSpecActorUnidirectional { + val latch = new CountDownLatch(1) } class RemoteActorSpecActorUnidirectional extends Actor { dispatcher = Dispatchers.newThreadBasedDispatcher(this) def receive = { case "OneWay" => - Global.oneWay.countDown + RemoteActorSpecActorUnidirectional.latch.countDown } } @@ -31,21 +32,6 @@ class RemoteActorSpecActorBidirectional extends Actor { } } -case class Send(actor: Actor) - -class RemoteActorSpecActorAsyncSender extends Actor { - def receive = { - case Send(actor: Actor) => - actor ! "Hello" - case "World" => - Global.remoteReply.countDown - } - - def send(actor: Actor) { - this ! Send(actor) - } -} - class SendOneWayAndReplyReceiverActor extends Actor { def receive = { case "Hello" => @@ -53,6 +39,9 @@ class SendOneWayAndReplyReceiverActor extends Actor { } } +object SendOneWayAndReplySenderActor { + val latch = new CountDownLatch(1) +} class SendOneWayAndReplySenderActor extends Actor { var state: Option[AnyRef] = None var sendTo: Actor = _ @@ -63,7 +52,7 @@ class SendOneWayAndReplySenderActor extends Actor { def receive = { case msg: AnyRef => state = Some(msg) - latch.countDown + SendOneWayAndReplySenderActor.latch.countDown } } @@ -104,7 +93,7 @@ class ClientInitiatedRemoteActorSpec extends JUnitSuite { actor.makeRemote(HOSTNAME, PORT1) actor.start actor ! "OneWay" - assert(Global.oneWay.await(1, TimeUnit.SECONDS)) + assert(RemoteActorSpecActorUnidirectional.latch.await(1, TimeUnit.SECONDS)) actor.stop } @@ -113,14 +102,12 @@ class ClientInitiatedRemoteActorSpec extends JUnitSuite { val actor = new SendOneWayAndReplyReceiverActor actor.makeRemote(HOSTNAME, PORT1) actor.start - val latch = new CountDownLatch(1) val sender = new SendOneWayAndReplySenderActor sender.setReplyToAddress(HOSTNAME, PORT2) sender.sendTo = actor - sender.latch = latch sender.start sender.sendOff - assert(latch.await(1, TimeUnit.SECONDS)) + assert(SendOneWayAndReplySenderActor.latch.await(1, TimeUnit.SECONDS)) assert(sender.state.isDefined === true) assert("World" === sender.state.get.asInstanceOf[String]) actor.stop @@ -128,7 +115,7 @@ class ClientInitiatedRemoteActorSpec extends JUnitSuite { } @Test - def shouldSendReplyAsync = { + def shouldSendBangBangMessageAndReceiveReply = { val actor = new RemoteActorSpecActorBidirectional actor.makeRemote(HOSTNAME, PORT1) actor.start @@ -138,23 +125,7 @@ class ClientInitiatedRemoteActorSpec extends JUnitSuite { } @Test - def shouldSendRemoteReply = { - implicit val timeout = 500000000L - val actor = new RemoteActorSpecActorBidirectional - actor.setReplyToAddress(HOSTNAME, PORT2) - actor.makeRemote(HOSTNAME, PORT2) - actor.start - - val sender = new RemoteActorSpecActorAsyncSender - sender.setReplyToAddress(HOSTNAME, PORT1) - sender.start - sender.send(actor) - assert(Global.remoteReply.await(1, TimeUnit.SECONDS)) - actor.stop - } - - @Test - def shouldSendReceiveException = { + def shouldSendAndReceiveRemoteException = { implicit val timeout = 500000000L val actor = new RemoteActorSpecActorBidirectional actor.makeRemote(HOSTNAME, PORT1) diff --git a/akka-core/src/test/scala/ServerInitiatedRemoteActorSpec.scala b/akka-core/src/test/scala/ServerInitiatedRemoteActorSpec.scala index eec242f7ae..d2d5795bc6 100644 --- a/akka-core/src/test/scala/ServerInitiatedRemoteActorSpec.scala +++ b/akka-core/src/test/scala/ServerInitiatedRemoteActorSpec.scala @@ -11,20 +11,18 @@ object ServerInitiatedRemoteActorSpec { val HOSTNAME = "localhost" val PORT = 9990 var server: RemoteServer = null + + case class Send(actor: Actor) - object Global { - val oneWay = new CountDownLatch(1) - var remoteReply = new CountDownLatch(1) + object RemoteActorSpecActorUnidirectional { + val latch = new CountDownLatch(1) } - class RemoteActorSpecActorUnidirectional extends Actor { - dispatcher = Dispatchers.newThreadBasedDispatcher(this) start def receive = { case "OneWay" => - println("================== ONEWAY") - Global.oneWay.countDown + RemoteActorSpecActorUnidirectional.latch.countDown } } @@ -38,15 +36,16 @@ object ServerInitiatedRemoteActorSpec { } } - case class Send(actor: Actor) - + object RemoteActorSpecActorAsyncSender { + val latch = new CountDownLatch(1) + } class RemoteActorSpecActorAsyncSender extends Actor { start def receive = { case Send(actor: Actor) => actor ! "Hello" case "World" => - Global.remoteReply.countDown + RemoteActorSpecActorAsyncSender.latch.countDown } def send(actor: Actor) { @@ -91,7 +90,7 @@ class ServerInitiatedRemoteActorSpec extends JUnitSuite { 5000L, HOSTNAME, PORT) val result = actor ! "OneWay" - assert(Global.oneWay.await(1, TimeUnit.SECONDS)) + assert(RemoteActorSpecActorUnidirectional.latch.await(1, TimeUnit.SECONDS)) actor.stop } @@ -113,12 +112,11 @@ class ServerInitiatedRemoteActorSpec extends JUnitSuite { "se.scalablesolutions.akka.actor.ServerInitiatedRemoteActorSpec$RemoteActorSpecActorBidirectional", timeout, HOSTNAME, PORT) - val sender = new RemoteActorSpecActorAsyncSender sender.setReplyToAddress(HOSTNAME, PORT) sender.start sender.send(actor) - assert(Global.remoteReply.await(1, TimeUnit.SECONDS)) + assert(RemoteActorSpecActorAsyncSender.latch.await(1, TimeUnit.SECONDS)) actor.stop } diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index b5c776fb80..5b2958ddf5 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -1,42 +1,12 @@ -/*------------------------------------------------------------------------------- - Copyright (C) 2009-2010 Scalable Solutions AB - - ---------------------------------------------------- - -------- sbt buildfile for the Akka project -------- - ---------------------------------------------------- - - Akka implements a unique hybrid of: - * Actors , which gives you: - * Simple and high-level abstractions for concurrency and parallelism. - * Asynchronous, non-blocking and highly performant event-driven programming model. - * Very lightweight event-driven processes (create ~6.5 million actors on 4 G RAM). - * Supervision hierarchies with let-it-crash semantics. For writing highly - fault-tolerant systems that never stop, systems that self-heal. - * Software Transactional Memory (STM). (Distributed transactions coming soon). - * Transactors: combine actors and STM into transactional actors. Allows you to - compose atomic message flows with automatic rollback and retry. - * Remoting: highly performant distributed actors with remote supervision and - error management. - * Cluster membership management. - - Akka also has a set of add-on modules: - * Persistence: A set of pluggable back-end storage modules that work in sync with the STM. - * Cassandra distributed and highly scalable database. - * MongoDB document database. - * Redis data structures database - * Camel: Expose Actors as Camel endpoints. - * REST (JAX-RS): Expose actors as REST services. - * Comet: Expose actors as Comet services. - * Security: Digest and Kerberos based security. - * Spring: Spring integration - * Guice: Guice integration - * Microkernel: Run Akka as a stand-alone kernel. - --------------------------------------------------------------------------------*/ + /*---------------------------------------------------------------------------\ +| Copyright (C) 2009-2010 Scalable Solutions AB | +\---------------------------------------------------------------------------*/ import sbt._ import sbt.CompileOrder._ + import scala.Array + import java.util.jar.Attributes import java.util.jar.Attributes.Name._ import java.io.File @@ -51,18 +21,11 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { val LIFT_VERSION = "2.0-scala280-SNAPSHOT" val SCALATEST_VERSION = "1.0.1-for-scala-2.8.0.Beta1-with-test-interfaces-0.3-SNAPSHOT" - // ------------------------------------------------------------ - lazy val akkaHome = { - val home = System.getenv("AKKA_HOME") - if (home == null) throw new Error( - "You need to set the $AKKA_HOME environment variable to the root of the Akka distribution") - Path.fromFile(home) - } + // ------------------------------------------------------------ val encodingUtf8 = List("-encoding", "UTF-8") - override def parallelExecution = false - lazy val deployPath = akkaHome / "deploy" - lazy val distPath = akkaHome / "dist" + lazy val deployPath = info.projectPath / "deploy" + lazy val distPath = info.projectPath / "dist" override def javaCompileOptions = JavaCompileOption("-Xlint:unchecked") :: super.javaCompileOptions.toList @@ -72,7 +35,7 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { // ------------------------------------------------------------ // repositories - val embeddedrepo = "embedded repo" at (akkaHome / "embedded-repo").asURL.toString + val embeddedrepo = "embedded repo" at (info.projectPath / "embedded-repo").asURL.toString val sunjdmk = "sunjdmk" at "http://wp5.e-taxonomy.eu/cdmlib/mavenrepo" val databinder = "DataBinder" at "http://databinder.net/repo" // val configgy = "Configgy" at "http://www.lag.net/repo" From e52347596a50b602e137025dcb92f070adbd8c0b Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Sat, 10 Apr 2010 00:50:57 +0200 Subject: [PATCH 10/27] Readded more SBinary functionality --- .../scala/remote/RemoteProtocolBuilder.scala | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/akka-core/src/main/scala/remote/RemoteProtocolBuilder.scala b/akka-core/src/main/scala/remote/RemoteProtocolBuilder.scala index 1156a34b27..65558dd997 100644 --- a/akka-core/src/main/scala/remote/RemoteProtocolBuilder.scala +++ b/akka-core/src/main/scala/remote/RemoteProtocolBuilder.scala @@ -4,7 +4,7 @@ package se.scalablesolutions.akka.remote -//import se.scalablesolutions.akka.serialization.Serializable.SBinary +import se.scalablesolutions.akka.serialization.Serializable.SBinary import se.scalablesolutions.akka.serialization.{Serializer, Serializable, SerializationProtocol} import se.scalablesolutions.akka.remote.protobuf.RemoteProtocol.{RemoteRequest, RemoteReply} @@ -14,7 +14,7 @@ object RemoteProtocolBuilder { private var SERIALIZER_JAVA: Serializer.Java = Serializer.Java private var SERIALIZER_JAVA_JSON: Serializer.JavaJSON = Serializer.JavaJSON private var SERIALIZER_SCALA_JSON: Serializer.ScalaJSON = Serializer.ScalaJSON - //private var SERIALIZER_SBINARY: Serializer.SBinary = Serializer.SBinary + private var SERIALIZER_SBINARY: Serializer.SBinary = Serializer.SBinary private var SERIALIZER_PROTOBUF: Serializer.Protobuf = Serializer.Protobuf @@ -26,9 +26,9 @@ object RemoteProtocolBuilder { def getMessage(request: RemoteRequest): Any = { request.getProtocol match { - //case SerializationProtocol.SBINARY => - // val renderer = Class.forName(new String(request.getMessageManifest.toByteArray)).newInstance.asInstanceOf[SBinary[_ <: AnyRef]] - // renderer.fromBytes(request.getMessage.toByteArray) + case SerializationProtocol.SBINARY => + val renderer = Class.forName(new String(request.getMessageManifest.toByteArray)).newInstance.asInstanceOf[SBinary[_ <: AnyRef]] + renderer.fromBytes(request.getMessage.toByteArray) case SerializationProtocol.SCALA_JSON => val manifest = SERIALIZER_JAVA.in(request.getMessageManifest.toByteArray, None).asInstanceOf[String] SERIALIZER_SCALA_JSON.in(request.getMessage.toByteArray, Some(Class.forName(manifest))) @@ -47,9 +47,9 @@ object RemoteProtocolBuilder { def getMessage(reply: RemoteReply): Any = { reply.getProtocol match { - //case SerializationProtocol.SBINARY => - // val renderer = Class.forName(new String(reply.getMessageManifest.toByteArray)).newInstance.asInstanceOf[SBinary[_ <: AnyRef]] - // renderer.fromBytes(reply.getMessage.toByteArray) + case SerializationProtocol.SBINARY => + val renderer = Class.forName(new String(reply.getMessageManifest.toByteArray)).newInstance.asInstanceOf[SBinary[_ <: AnyRef]] + renderer.fromBytes(reply.getMessage.toByteArray) case SerializationProtocol.SCALA_JSON => val manifest = SERIALIZER_JAVA.in(reply.getMessageManifest.toByteArray, None).asInstanceOf[String] SERIALIZER_SCALA_JSON.in(reply.getMessage.toByteArray, Some(Class.forName(manifest))) @@ -67,12 +67,12 @@ object RemoteProtocolBuilder { } def setMessage(message: Any, builder: RemoteRequest.Builder) = { - /*if (message.isInstanceOf[Serializable.SBinary[_]]) { + if (message.isInstanceOf[Serializable.SBinary[_]]) { val serializable = message.asInstanceOf[Serializable.SBinary[_ <: Any]] builder.setProtocol(SerializationProtocol.SBINARY) builder.setMessage(ByteString.copyFrom(serializable.toBytes)) builder.setMessageManifest(ByteString.copyFrom(serializable.getClass.getName.getBytes)) - } else*/ if (message.isInstanceOf[Message]) { + } else if (message.isInstanceOf[Message]) { val serializable = message.asInstanceOf[Message] builder.setProtocol(SerializationProtocol.PROTOBUF) builder.setMessage(ByteString.copyFrom(serializable.toByteArray)) @@ -95,12 +95,12 @@ object RemoteProtocolBuilder { } def setMessage(message: Any, builder: RemoteReply.Builder) = { - /*if (message.isInstanceOf[Serializable.SBinary[_]]) { + if (message.isInstanceOf[Serializable.SBinary[_]]) { val serializable = message.asInstanceOf[Serializable.SBinary[_ <: Any]] builder.setProtocol(SerializationProtocol.SBINARY) builder.setMessage(ByteString.copyFrom(serializable.toBytes)) builder.setMessageManifest(ByteString.copyFrom(serializable.getClass.getName.getBytes)) - } else*/ if (message.isInstanceOf[Message]) { + } else if (message.isInstanceOf[Message]) { val serializable = message.asInstanceOf[Message] builder.setProtocol(SerializationProtocol.PROTOBUF) builder.setMessage(ByteString.copyFrom(serializable.toByteArray)) From 7883b73052fd6ee94f2699163111518f9ff4c66d Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Sat, 10 Apr 2010 23:59:40 +0200 Subject: [PATCH 11/27] Refactored _isEventBased into the MessageDispatcher --- akka-core/src/main/scala/actor/Actor.scala | 28 ++++++++----------- .../ExecutorBasedEventDrivenDispatcher.scala | 2 ++ ...sedEventDrivenWorkStealingDispatcher.scala | 2 ++ .../src/main/scala/dispatch/Reactor.scala | 1 + ...sedSingleThreadEventDrivenDispatcher.scala | 2 ++ ...BasedThreadPoolEventDrivenDispatcher.scala | 2 ++ .../dispatch/ThreadBasedDispatcher.scala | 2 ++ 7 files changed, 22 insertions(+), 17 deletions(-) diff --git a/akka-core/src/main/scala/actor/Actor.scala b/akka-core/src/main/scala/actor/Actor.scala index 4bc3a9dc31..b80ce3afb1 100644 --- a/akka-core/src/main/scala/actor/Actor.scala +++ b/akka-core/src/main/scala/actor/Actor.scala @@ -232,7 +232,6 @@ trait Actor extends TransactionManagement with Logging { @volatile private[this] var _isRunning = false @volatile private[this] var _isSuspended = true @volatile private[this] var _isShutDown = false - @volatile private[this] var _isEventBased: Boolean = false @volatile private[akka] var _isKilled = false private var _hotswap: Option[PartialFunction[Any, Unit]] = None private[akka] var _remoteAddress: Option[InetSocketAddress] = None @@ -294,11 +293,7 @@ trait Actor extends TransactionManagement with Logging { * The default is also that all actors that are created and spawned from within this actor * is sharing the same dispatcher as its creator. */ - protected[akka] var messageDispatcher: MessageDispatcher = { - val dispatcher = Dispatchers.globalExecutorBasedEventDrivenDispatcher - _isEventBased = dispatcher.isInstanceOf[ExecutorBasedEventDrivenDispatcher] - dispatcher - } + protected[akka] var messageDispatcher: MessageDispatcher = Dispatchers.globalExecutorBasedEventDrivenDispatcher /** * User overridable callback/setting. @@ -513,8 +508,11 @@ trait Actor extends TransactionManagement with Logging { if (isActiveObject) throw e else None } - getResultOrThrowException(future) - } else throw new IllegalStateException( + + if (future.exception.isDefined) throw future.exception.get._2 + else future.result.asInstanceOf[Option[T]] + } + else throw new IllegalStateException( "Actor has not been started, you need to invoke 'actor.start' before using it") } @@ -593,7 +591,6 @@ trait Actor extends TransactionManagement with Logging { messageDispatcher.unregister(this) messageDispatcher = md messageDispatcher.register(this) - _isEventBased = messageDispatcher.isInstanceOf[ExecutorBasedEventDrivenDispatcher] } else throw new IllegalArgumentException( "Can not swap dispatcher for " + toString + " after it has been started") } @@ -816,7 +813,7 @@ trait Actor extends TransactionManagement with Logging { RemoteClient.clientFor(_remoteAddress.get).send(requestBuilder.build, None) } else { val invocation = new MessageInvocation(this, message, sender.map(Left(_)), transactionSet.get) - if (_isEventBased) { + if (messageDispatcher.usesActorMailbox) { _mailbox.add(invocation) if (_isSuspended) invocation.send } @@ -849,10 +846,11 @@ trait Actor extends TransactionManagement with Logging { val future = if (senderFuture.isDefined) senderFuture.get else new DefaultCompletableFuture(timeout) val invocation = new MessageInvocation(this, message, Some(Right(future)), transactionSet.get) - if (_isEventBased) { + + if (messageDispatcher.usesActorMailbox) _mailbox.add(invocation) - invocation.send - } else invocation.send + + invocation.send future } } @@ -958,10 +956,6 @@ trait Actor extends TransactionManagement with Logging { } } - private def getResultOrThrowException[T](future: Future): Option[T] = - if (future.exception.isDefined) throw future.exception.get._2 - else future.result.asInstanceOf[Option[T]] - private def base: PartialFunction[Any, Unit] = lifeCycles orElse (_hotswap getOrElse receive) private val lifeCycles: PartialFunction[Any, Unit] = { diff --git a/akka-core/src/main/scala/dispatch/ExecutorBasedEventDrivenDispatcher.scala b/akka-core/src/main/scala/dispatch/ExecutorBasedEventDrivenDispatcher.scala index 705c3ee142..0c624c2e3a 100644 --- a/akka-core/src/main/scala/dispatch/ExecutorBasedEventDrivenDispatcher.scala +++ b/akka-core/src/main/scala/dispatch/ExecutorBasedEventDrivenDispatcher.scala @@ -94,6 +94,8 @@ class ExecutorBasedEventDrivenDispatcher(_name: String) extends MessageDispatche active = false references.clear } + + def usesActorMailbox = true def ensureNotActive: Unit = if (active) throw new IllegalStateException( "Can't build a new thread pool for a dispatcher that is already up and running") diff --git a/akka-core/src/main/scala/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcher.scala b/akka-core/src/main/scala/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcher.scala index a96f5c5e76..28fe624b86 100644 --- a/akka-core/src/main/scala/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcher.scala +++ b/akka-core/src/main/scala/dispatch/ExecutorBasedEventDrivenWorkStealingDispatcher.scala @@ -199,6 +199,8 @@ class ExecutorBasedEventDrivenWorkStealingDispatcher(_name: String) extends Mess pooledActors.remove(actor) super.unregister(actor) } + + def usesActorMailbox = true private def verifyActorsAreOfSameType(newActor: Actor) = { actorType match { diff --git a/akka-core/src/main/scala/dispatch/Reactor.scala b/akka-core/src/main/scala/dispatch/Reactor.scala index f9db74190f..3f300b1c52 100644 --- a/akka-core/src/main/scala/dispatch/Reactor.scala +++ b/akka-core/src/main/scala/dispatch/Reactor.scala @@ -68,6 +68,7 @@ trait MessageDispatcher extends Logging { } def canBeShutDown: Boolean = references.isEmpty def isShutdown: Boolean + def usesActorMailbox : Boolean } trait MessageDemultiplexer { diff --git a/akka-core/src/main/scala/dispatch/ReactorBasedSingleThreadEventDrivenDispatcher.scala b/akka-core/src/main/scala/dispatch/ReactorBasedSingleThreadEventDrivenDispatcher.scala index 15af513d62..fc99cf88d2 100644 --- a/akka-core/src/main/scala/dispatch/ReactorBasedSingleThreadEventDrivenDispatcher.scala +++ b/akka-core/src/main/scala/dispatch/ReactorBasedSingleThreadEventDrivenDispatcher.scala @@ -37,6 +37,8 @@ class ReactorBasedSingleThreadEventDrivenDispatcher(name: String) extends Abstra } def isShutdown = !active + + def usesActorMailbox = false class Demultiplexer(private val messageQueue: ReactiveMessageQueue) extends MessageDemultiplexer { diff --git a/akka-core/src/main/scala/dispatch/ReactorBasedThreadPoolEventDrivenDispatcher.scala b/akka-core/src/main/scala/dispatch/ReactorBasedThreadPoolEventDrivenDispatcher.scala index 941e701410..3f33d4ffc0 100644 --- a/akka-core/src/main/scala/dispatch/ReactorBasedThreadPoolEventDrivenDispatcher.scala +++ b/akka-core/src/main/scala/dispatch/ReactorBasedThreadPoolEventDrivenDispatcher.scala @@ -134,6 +134,8 @@ class ReactorBasedThreadPoolEventDrivenDispatcher(_name: String) if (fair) true else nrOfBusyMessages < 100 } + + def usesActorMailbox = false def ensureNotActive: Unit = if (active) throw new IllegalStateException( "Can't build a new thread pool for a dispatcher that is already up and running") diff --git a/akka-core/src/main/scala/dispatch/ThreadBasedDispatcher.scala b/akka-core/src/main/scala/dispatch/ThreadBasedDispatcher.scala index 8b1463f655..fbfffc999e 100644 --- a/akka-core/src/main/scala/dispatch/ThreadBasedDispatcher.scala +++ b/akka-core/src/main/scala/dispatch/ThreadBasedDispatcher.scala @@ -41,6 +41,8 @@ class ThreadBasedDispatcher private[akka] (val name: String, val messageHandler: def isShutdown = !active + def usesActorMailbox = false + def shutdown = if (active) { log.debug("Shutting down ThreadBasedDispatcher [%s]", name) active = false From e34819007edbc1b93a92d2db16b1cc2ccf10975f Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Sun, 11 Apr 2010 00:00:06 +0200 Subject: [PATCH 12/27] Moved a runtime error to compile time --- akka-core/src/main/scala/actor/Actor.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/akka-core/src/main/scala/actor/Actor.scala b/akka-core/src/main/scala/actor/Actor.scala index b80ce3afb1..627913407b 100644 --- a/akka-core/src/main/scala/actor/Actor.scala +++ b/akka-core/src/main/scala/actor/Actor.scala @@ -221,7 +221,7 @@ object Actor extends Logging { * @author Jonas Bonér */ trait Actor extends TransactionManagement with Logging { - implicit protected val self: Option[Actor] = Some(this) + implicit protected val self: Some[Actor] = Some(this) // Only mutable for RemoteServer in order to maintain identity across nodes private[akka] var _uuid = UUID.newUuid.toString @@ -548,11 +548,10 @@ trait Actor extends TransactionManagement with Logging { *

* Works with both '!' and '!!'. */ - def forward(message: Any)(implicit sender: Option[Actor] = None) = { + def forward(message: Any)(implicit sender: Some[Actor]) = { if (_isKilled) throw new ActorKilledException("Actor [" + toString + "] has been killed, can't respond to messages") if (_isRunning) { - val forwarder = sender.getOrElse(throw new IllegalStateException("Can't forward message when the forwarder/mediator is not an actor")) - forwarder.replyTo match { + sender.get.replyTo match { case Some(Left(actor)) => postMessageToMailbox(message, Some(actor)) case Some(Right(future)) => postMessageToMailboxAndCreateFutureResultWithTimeout(message, timeout, Some(future)) case _ => throw new IllegalStateException("Can't forward message when initial sender is not an actor") From 90b28210f931b91dbf85b57da458a86dbd295d9d Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Sun, 11 Apr 2010 00:00:18 +0200 Subject: [PATCH 13/27] Documented replyTo --- akka-core/src/main/scala/actor/Actor.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/akka-core/src/main/scala/actor/Actor.scala b/akka-core/src/main/scala/actor/Actor.scala index 627913407b..0b5aecf3f0 100644 --- a/akka-core/src/main/scala/actor/Actor.scala +++ b/akka-core/src/main/scala/actor/Actor.scala @@ -251,7 +251,10 @@ trait Actor extends TransactionManagement with Logging { // ==================================== /** - * TODO: Document replyTo + * Holds the reference to the sender of the currently processed message. + * Is None if no sender was specified + * Is Some(Left(Actor)) if sender is an actor + * Is Some(Right(CompletableFuture)) if sender is holding on to a Future for the result */ protected var replyTo: Option[Either[Actor,CompletableFuture]] = None From bc49036d400ffc3795b441611911d5cda1a77279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Mon, 12 Apr 2010 07:03:52 +0200 Subject: [PATCH 14/27] fixed bug in config file --- config/akka-reference.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/akka-reference.conf b/config/akka-reference.conf index 34cc9eaec8..a329b1ef10 100644 --- a/config/akka-reference.conf +++ b/config/akka-reference.conf @@ -61,12 +61,12 @@ hostname = "localhost" port = 9999 connection-timeout = 1000 # in millis (1 sec default) - + reconnect-delay = 5000 # in millis (5 sec default) read-timeout = 10000 # in millis (10 sec default) - + From a6b84831bbbfcc02b613458d3b350441500eaa9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Mon, 12 Apr 2010 07:29:24 +0200 Subject: [PATCH 15/27] added compile options --- project/build/AkkaProject.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index 5b2958ddf5..41a51b8c8c 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -5,8 +5,6 @@ import sbt._ import sbt.CompileOrder._ -import scala.Array - import java.util.jar.Attributes import java.util.jar.Attributes.Name._ import java.io.File @@ -22,11 +20,12 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { val SCALATEST_VERSION = "1.0.1-for-scala-2.8.0.Beta1-with-test-interfaces-0.3-SNAPSHOT" // ------------------------------------------------------------ - val encodingUtf8 = List("-encoding", "UTF-8") - lazy val deployPath = info.projectPath / "deploy" lazy val distPath = info.projectPath / "dist" + override def compileOptions = super.compileOptions ++ + Seq("-deprecation", "-Xmigration", "-Xcheckinit", "-Xstrict-warnings", "-Xwarninit", "-encoding", "utf8").map(x => CompileOption(x)) + override def javaCompileOptions = JavaCompileOption("-Xlint:unchecked") :: super.javaCompileOptions.toList def distName = "%s_%s-%s.zip".format(name, buildScalaVersion, version) From 4728c3c1f07c26cd00f8366ffc0763cd28c9672b Mon Sep 17 00:00:00 2001 From: Michael Kober Date: Wed, 14 Apr 2010 09:56:01 +0200 Subject: [PATCH 16/27] implemented link/unlink for active objects --- .../src/main/scala/actor/ActiveObject.scala | 84 +++++++++++++++---- akka-core/src/main/scala/actor/Actor.scala | 18 ++-- .../akka/spring/foo/Bar.java | 6 ++ 3 files changed, 85 insertions(+), 23 deletions(-) diff --git a/akka-core/src/main/scala/actor/ActiveObject.scala b/akka-core/src/main/scala/actor/ActiveObject.scala index e3bd9ef943..cb67ae0244 100644 --- a/akka-core/src/main/scala/actor/ActiveObject.scala +++ b/akka-core/src/main/scala/actor/ActiveObject.scala @@ -4,6 +4,7 @@ package se.scalablesolutions.akka.actor +import _root_.se.scalablesolutions.akka.config.FaultHandlingStrategy import se.scalablesolutions.akka.remote.protobuf.RemoteProtocol.RemoteRequest import se.scalablesolutions.akka.remote.{RemoteProtocolBuilder, RemoteClient, RemoteRequestIdFactory} import se.scalablesolutions.akka.dispatch.{MessageDispatcher, Future} @@ -199,18 +200,71 @@ object ActiveObject { proxy.asInstanceOf[T] } -// Jan Kronquist: started work on issue 121 -// def actorFor(obj: AnyRef): Option[Actor] = { -// ActorRegistry.actorsFor(classOf[Dispatcher]).find(a=>a.target == Some(obj)) -// } -// -// def link(supervisor: AnyRef, activeObject: AnyRef) = { -// actorFor(supervisor).get !! Link(actorFor(activeObject).get) -// } -// -// def unlink(supervisor: AnyRef, activeObject: AnyRef) = { -// actorFor(supervisor).get !! Unlink(actorFor(activeObject).get) -// } + /** + * Get the underlying dispatcher actor for the given active object. + */ + def actorFor(obj: AnyRef): Option[Actor] = { + ActorRegistry.actorsFor(classOf[Dispatcher]).find(a=>a.target == Some(obj)) + } + + /** + * Links an other active object to this active object. + * @param supervisor the supervisor active object + * @param supervised the active object to link + */ + def link(supervisor: AnyRef, supervised: AnyRef) = { + val supervisorActor = actorFor(supervisor).getOrElse(throw new IllegalStateException("Can't link when the supervisor is not an active object")) + val supervisedActor = actorFor(supervised).getOrElse(throw new IllegalStateException("Can't link when the supervised is not an active object")) + supervisorActor !! Link(supervisedActor) + } + + /** + * Links an other active object to this active object and sets the fault handling for the supervisor. + * @param supervisor the supervisor active object + * @param supervised the active object to link + * @param handler fault handling strategy + * @param trapExceptions array of exceptions that should be handled by the supervisor + */ + def link(supervisor: AnyRef, supervised: AnyRef, handler: FaultHandlingStrategy, trapExceptions: Array[Class[_ <: Throwable]]) = { + val supervisorActor = actorFor(supervisor).getOrElse(throw new IllegalStateException("Can't link when the supervisor is not an active object")) + val supervisedActor = actorFor(supervised).getOrElse(throw new IllegalStateException("Can't link when the supervised is not an active object")) + supervisorActor.trapExit = trapExceptions.toList + supervisorActor.faultHandler = Some(handler) + supervisorActor !! Link(supervisedActor) + } + + /** + * Unlink the supervised active object from the supervisor. + * @param supervisor the supervisor active object + * @param supervised the active object to unlink + */ + def unlink(supervisor: AnyRef, supervised: AnyRef) = { + val supervisorActor = actorFor(supervisor).getOrElse(throw new IllegalStateException("Can't unlink when the supervisor is not an active object")) + val supervisedActor = actorFor(supervised).getOrElse(throw new IllegalStateException("Can't unlink when the supervised is not an active object")) + supervisorActor !! Unlink(supervisedActor) + } + + /** + * Sets the trap exit for the given supervisor active object. + * @param supervisor the supervisor active object + * @param trapExceptions array of exceptions that should be handled by the supervisor + */ + def trapExit(supervisor: AnyRef, trapExceptions: Array[Class[_ <: Throwable]]) = { + val supervisorActor = actorFor(supervisor).getOrElse(throw new IllegalStateException("Can't set trap exceptions when the supervisor is not an active object")) + supervisorActor.trapExit = trapExceptions.toList + this + } + + /** + * Sets the fault handling strategy for the given supervisor active object. + * @param supervisor the supervisor active object + * @param handler fault handling strategy + */ + def faultHandler(supervisor: AnyRef, handler: FaultHandlingStrategy) = { + val supervisorActor = actorFor(supervisor).getOrElse(throw new IllegalStateException("Can't set fault handler when the supervisor is not an active object")) + supervisorActor.faultHandler = Some(handler) + this + } private[akka] def supervise(restartStrategy: RestartStrategy, components: List[Supervise]): Supervisor = { val factory = SupervisorFactory(SupervisorConfig(restartStrategy, components)) @@ -366,7 +420,7 @@ private[akka] sealed class ActiveObjectAspect { } // Jan Kronquist: started work on issue 121 -// private[akka] case class Link(val actor: Actor) +private[akka] case class Link(val actor: Actor) object Dispatcher { val ZERO_ITEM_CLASS_ARRAY = Array[Class[_]]() @@ -434,8 +488,8 @@ private[akka] class Dispatcher(transactionalRequired: Boolean, val callbacks: Op if (isOneWay) joinPoint.proceed else reply(joinPoint.proceed) // Jan Kronquist: started work on issue 121 -// case Link(target) => -// link(target) + case Link(target) => link(target) + case Unlink(target) => unlink(target) case unexpected => throw new IllegalStateException("Unexpected message [" + unexpected + "] sent to [" + this + "]") } diff --git a/akka-core/src/main/scala/actor/Actor.scala b/akka-core/src/main/scala/actor/Actor.scala index 4bc3a9dc31..8c38769579 100644 --- a/akka-core/src/main/scala/actor/Actor.scala +++ b/akka-core/src/main/scala/actor/Actor.scala @@ -52,6 +52,7 @@ case class HotSwap(code: Option[PartialFunction[Any, Unit]]) extends LifeCycleMe case class Restart(reason: Throwable) extends LifeCycleMessage case class Exit(dead: Actor, killer: Throwable) extends LifeCycleMessage case class Unlink(child: Actor) extends LifeCycleMessage +case class UnlinkAndStop(child: Actor) extends LifeCycleMessage case object Kill extends LifeCycleMessage class ActorKilledException private[akka](message: String) extends RuntimeException(message) @@ -318,7 +319,7 @@ trait Actor extends TransactionManagement with Logging { * trapExit = List(classOf[MyApplicationException], classOf[MyApplicationError]) * */ - protected var trapExit: List[Class[_ <: Throwable]] = Nil + protected[akka] var trapExit: List[Class[_ <: Throwable]] = Nil /** * User overridable callback/setting. @@ -331,7 +332,7 @@ trait Actor extends TransactionManagement with Logging { * faultHandler = Some(OneForOneStrategy(maxNrOfRetries, withinTimeRange)) * */ - protected var faultHandler: Option[FaultHandlingStrategy] = None + protected[akka] var faultHandler: Option[FaultHandlingStrategy] = None /** * User overridable callback/setting. @@ -965,11 +966,12 @@ trait Actor extends TransactionManagement with Logging { private def base: PartialFunction[Any, Unit] = lifeCycles orElse (_hotswap getOrElse receive) private val lifeCycles: PartialFunction[Any, Unit] = { - case HotSwap(code) => _hotswap = code - case Restart(reason) => restart(reason) - case Exit(dead, reason) => handleTrapExit(dead, reason) - case Unlink(child) => unlink(child); child.stop - case Kill => throw new ActorKilledException("Actor [" + toString + "] was killed by a Kill message") + case HotSwap(code) => _hotswap = code + case Restart(reason) => restart(reason) + case Exit(dead, reason) => handleTrapExit(dead, reason) + case Unlink(child) => unlink(child) + case UnlinkAndStop(child) => unlink(child); child.stop + case Kill => throw new ActorKilledException("Actor [" + toString + "] was killed by a Kill message") } private[this] def handleTrapExit(dead: Actor, reason: Throwable): Unit = { @@ -1002,7 +1004,7 @@ trait Actor extends TransactionManagement with Logging { // if last temporary actor is gone, then unlink me from supervisor if (getLinkedActors.isEmpty) { Actor.log.info("All linked actors have died permanently (they were all configured as TEMPORARY)\n\tshutting down and unlinking supervisor actor as well [%s].", actor.id) - _supervisor.foreach(_ ! Unlink(this)) + _supervisor.foreach(_ ! UnlinkAndStop(this)) } } } diff --git a/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/Bar.java b/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/Bar.java index 7e21aaea8f..24e02a3fd6 100644 --- a/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/Bar.java +++ b/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/Bar.java @@ -1,10 +1,16 @@ package se.scalablesolutions.akka.spring.foo; +import java.io.IOException; + public class Bar implements IBar { @Override public String getBar() { return "bar"; } + + public void throwsIOException() throws IOException { + throw new IOException("some IO went wrong"); + } } From 4117d943af18c4d62517bfc5fbcf9e56ba52fa12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Wed, 14 Apr 2010 15:37:29 +0200 Subject: [PATCH 17/27] fixed bug with ignoring timeout in Java API --- .../src/main/scala/actor/ActiveObject.scala | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/akka-core/src/main/scala/actor/ActiveObject.scala b/akka-core/src/main/scala/actor/ActiveObject.scala index e3bd9ef943..17cd482582 100644 --- a/akka-core/src/main/scala/actor/ActiveObject.scala +++ b/akka-core/src/main/scala/actor/ActiveObject.scala @@ -35,7 +35,7 @@ object Annotations { */ object ActiveObject { val AKKA_CAMEL_ROUTING_SCHEME = "akka" - private[actor] val AW_PROXY_PREFIX = "$$ProxiedByAW".intern + private[actor] val AW_PROXY_PREFIX = "$$ProxiedByAW".intern def newInstance[T](target: Class[T], timeout: Long): T = newInstance(target, new Dispatcher(false, None), None, timeout) @@ -227,19 +227,19 @@ private[akka] object AspectInitRegistry { val init = initializations.get(target) initializations.remove(target) init - } + } def register(target: AnyRef, init: AspectInit) = initializations.put(target, init) } private[akka] sealed case class AspectInit( val target: Class[_], - val actor: Dispatcher, + val actor: Dispatcher, val remoteAddress: Option[InetSocketAddress], val timeout: Long) { def this(target: Class[_],actor: Dispatcher, timeout: Long) = this(target, actor, None, timeout) } - + /** * AspectWerkz Aspect that is turning POJOs into Active Object. * Is deployed on a 'per-instance' basis. @@ -260,7 +260,7 @@ private[akka] sealed class ActiveObjectAspect { if (!isInitialized) { val init = AspectInitRegistry.initFor(joinPoint.getThis) target = init.target - actor = init.actor + actor = init.actor remoteAddress = init.remoteAddress timeout = init.timeout isInitialized = true @@ -279,7 +279,7 @@ private[akka] sealed class ActiveObjectAspect { (actor ! Invocation(joinPoint, true, true) ).asInstanceOf[AnyRef] } else { - val result = actor !! Invocation(joinPoint, false, isVoid(rtti)) + val result = actor !! (Invocation(joinPoint, false, isVoid(rtti)), timeout) if (result.isDefined) result.get else throw new IllegalStateException("No result defined for invocation [" + joinPoint + "]") } @@ -319,7 +319,7 @@ private[akka] sealed class ActiveObjectAspect { val (_, cause) = future.exception.get throw cause } else future.result.asInstanceOf[Option[T]] - + private def isOneWay(rtti: MethodRtti) = rtti.getMethod.isAnnotationPresent(Annotations.oneway) private def isVoid(rtti: MethodRtti) = rtti.getMethod.getReturnType == java.lang.Void.TYPE @@ -370,7 +370,7 @@ private[akka] sealed class ActiveObjectAspect { object Dispatcher { val ZERO_ITEM_CLASS_ARRAY = Array[Class[_]]() - val ZERO_ITEM_OBJECT_ARRAY = Array[Object]() + val ZERO_ITEM_OBJECT_ARRAY = Array[Object]() } /** @@ -408,7 +408,7 @@ private[akka] class Dispatcher(transactionalRequired: Boolean, val callbacks: Op "Could not find post restart method [" + post + "] \nin [" + targetClass.getName + "]. \nIt must have a zero argument definition.") }) } - // See if we have any annotation defined restart callbacks + // See if we have any annotation defined restart callbacks if (!preRestart.isDefined) preRestart = methods.find(m => m.isAnnotationPresent(Annotations.prerestart)) if (!postRestart.isDefined) postRestart = methods.find(m => m.isAnnotationPresent(Annotations.postrestart)) @@ -421,7 +421,7 @@ private[akka] class Dispatcher(transactionalRequired: Boolean, val callbacks: Op if (preRestart.isDefined) preRestart.get.setAccessible(true) if (postRestart.isDefined) postRestart.get.setAccessible(true) - + // see if we have a method annotated with @inittransactionalstate, if so invoke it initTxState = methods.find(m => m.isAnnotationPresent(Annotations.inittransactionalstate)) if (initTxState.isDefined && initTxState.get.getParameterTypes.length != 0) throw new IllegalStateException("Method annotated with @inittransactionalstate must have a zero argument definition") @@ -486,6 +486,6 @@ private[akka] class Dispatcher(transactionalRequired: Boolean, val callbacks: Op if (!unserializable && hasMutableArgument) { val copyOfArgs = Serializer.Java.deepClone(args) joinPoint.getRtti.asInstanceOf[MethodRtti].setParameterValues(copyOfArgs.asInstanceOf[Array[AnyRef]]) - } + } } } From 2abad7d4be4922bc43e682bf4d1cddcceab7d09f Mon Sep 17 00:00:00 2001 From: Debasish Ghosh Date: Wed, 14 Apr 2010 23:50:26 +0530 Subject: [PATCH 18/27] Redis client now implements pubsub. Also included a sample app for RedisPubSub in akka-samples --- .../src/main/scala/RedisPubSubServer.scala | 42 +++++++ .../src/main/scala/RedisPubSub.scala | 103 ++++++++++++++++++ .../redisclient-2.8.0.Beta1-1.3-SNAPSHOT.jar | Bin 118217 -> 161101 bytes project/build/AkkaProject.scala | 3 + 4 files changed, 148 insertions(+) create mode 100644 akka-persistence/akka-persistence-redis/src/main/scala/RedisPubSubServer.scala create mode 100644 akka-samples/akka-sample-pubsub/src/main/scala/RedisPubSub.scala diff --git a/akka-persistence/akka-persistence-redis/src/main/scala/RedisPubSubServer.scala b/akka-persistence/akka-persistence-redis/src/main/scala/RedisPubSubServer.scala new file mode 100644 index 0000000000..c5621361fb --- /dev/null +++ b/akka-persistence/akka-persistence-redis/src/main/scala/RedisPubSubServer.scala @@ -0,0 +1,42 @@ +package se.scalablesolutions.akka.persistence.redis + +import se.scalablesolutions.akka.actor.Actor +import com.redis._ + +sealed trait Msg +case class Subscribe(channels: Array[String]) extends Msg +case class Register(callback: PubSubMessage => Any) extends Msg +case class Unsubscribe(channels: Array[String]) extends Msg +case object UnsubscribeAll extends Msg +case class Publish(channel: String, msg: String) extends Msg + +class Subscriber(client: RedisClient) extends Actor { + var callback: PubSubMessage => Any = { m => } + + def receive = { + case Subscribe(channels) => + client.subscribe(channels.head, channels.tail: _*)(callback) + reply(true) + + case Register(cb) => + callback = cb + reply(true) + + case Unsubscribe(channels) => + client.unsubscribe(channels.head, channels.tail: _*) + reply(true) + + case UnsubscribeAll => + client.unsubscribe + reply(true) + } +} + +class Publisher(client: RedisClient) extends Actor { + def receive = { + case Publish(channel, message) => + client.publish(channel, message) + reply(true) + } +} + diff --git a/akka-samples/akka-sample-pubsub/src/main/scala/RedisPubSub.scala b/akka-samples/akka-sample-pubsub/src/main/scala/RedisPubSub.scala new file mode 100644 index 0000000000..ee14c2880d --- /dev/null +++ b/akka-samples/akka-sample-pubsub/src/main/scala/RedisPubSub.scala @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2009-2010 Scalable Solutions AB . + */ + +package sample.pubsub + +import com.redis.{RedisClient, PubSubMessage, S, U, M} +import se.scalablesolutions.akka.persistence.redis._ + +/** + * Sample Akka application for Redis PubSub + * + * Prerequisite: Need Redis Server running (the version that supports pubsub) + * + * 1. Download redis from http://github.com/antirez/redis + * 2. build using "make" + * 3. Run server as ./redis-server + * + * For running this sample application :- + * + * 1. Open a shell and set AKKA_HOME to the distribution root + * 2. cd $AKKA_HOME + * 3. sbt console + * 4. import sample.pubsub._ + * 5. Sub.sub("a", "b") // starts Subscription server & subscribes to channels "a" and "b" + * + * 6. Open up another shell similarly as the above and set AKKA_HOME + * 7. cd $AKKA_HOME + * 8. sbt console + * 9. import sample.pubsub._ + * 10. Pub.publish("a", "hello") // the first shell should get the message + * 11. Pub.publish("c", "hi") // the first shell should NOT get this message + * + * 12. Open up a redis-client from where you installed redis and issue a publish command + * ./redis-cli publish a "hi there" ## the first shell should get the message + * + * 13. Go back to the first shell + * 14. Sub.unsub("a") // should unsubscribe the first shell from channel "a" + * + * 15. Study the callback function defined below. It supports many other message formats. + * In the second shell window do the following: + * scala> Pub.publish("b", "+c") // will subscribe the first window to channel "c" + * scala> Pub.publish("b", "+d") // will subscribe the first window to channel "d" + * scala> Pub.publish("b", "-c") // will unsubscribe the first window from channel "c" + * scala> Pub.publish("b", "exit") // will unsubscribe the first window from all channels + */ + +object Pub { + println("starting publishing service ..") + val r = new RedisClient("localhost", 6379) + val p = new Publisher(r) + p.start + + def publish(channel: String, message: String) = { + p ! Publish(channel, message) + } +} + +object Sub { + println("starting subscription service ..") + val r = new RedisClient("localhost", 6379) + val s = new Subscriber(r) + s.start + s ! Register(callback) + + def sub(channels: String*) = { + s ! Subscribe(channels.toArray) + } + + def unsub(channels: String*) = { + s ! Unsubscribe(channels.toArray) + } + + def callback(pubsub: PubSubMessage) = pubsub match { + case S(channel, no) => println("subscribed to " + channel + " and count = " + no) + case U(channel, no) => println("unsubscribed from " + channel + " and count = " + no) + case M(channel, msg) => + msg match { + // exit will unsubscribe from all channels and stop subscription service + case "exit" => + println("unsubscribe all ..") + r.unsubscribe + + // message "+x" will subscribe to channel x + case x if x startsWith "+" => + val s: Seq[Char] = x + s match { + case Seq('+', rest @ _*) => r.subscribe(rest.toString){ m => } + } + + // message "-x" will unsubscribe from channel x + case x if x startsWith "-" => + val s: Seq[Char] = x + s match { + case Seq('-', rest @ _*) => r.unsubscribe(rest.toString) + } + + // other message receive + case x => + println("received message on channel " + channel + " as : " + x) + } + } +} diff --git a/embedded-repo/com/redis/redisclient/2.8.0.Beta1-1.3-SNAPSHOT/redisclient-2.8.0.Beta1-1.3-SNAPSHOT.jar b/embedded-repo/com/redis/redisclient/2.8.0.Beta1-1.3-SNAPSHOT/redisclient-2.8.0.Beta1-1.3-SNAPSHOT.jar index 0daede37f0471d224388f4634d63c854642064ce..52125e6d5270851b9c045c6dc83764bd94cac1d2 100644 GIT binary patch delta 74689 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

)!C2IdS4j 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 !*|cpunVO!Bf&G^CI;xrmuXb)( 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 diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index 41a51b8c8c..b0466d5ec8 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -301,6 +301,7 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { } class AkkaSampleChatProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) + class AkkaSamplePubSubProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) class AkkaSampleLiftProject(info: ProjectInfo) extends AkkaDefaultProject(info, deployPath) { val commons_logging = "commons-logging" % "commons-logging" % "1.1.1" % "compile" @@ -335,6 +336,8 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { class AkkaSamplesParentProject(info: ProjectInfo) extends ParentProject(info) { lazy val akka_sample_chat = project("akka-sample-chat", "akka-sample-chat", new AkkaSampleChatProject(_), akka_kernel) + lazy val akka_sample_pubsub = project("akka-sample-pubsub", "akka-sample-pubsub", + new AkkaSamplePubSubProject(_), akka_kernel) lazy val akka_sample_lift = project("akka-sample-lift", "akka-sample-lift", new AkkaSampleLiftProject(_), akka_kernel) lazy val akka_sample_rest_java = project("akka-sample-rest-java", "akka-sample-rest-java", From 475649077208f38476ae2740a489f29e0717c9a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Wed, 14 Apr 2010 20:47:19 +0200 Subject: [PATCH 19/27] Added AtomicTemplate to allow atomic blocks from Java code --- .../src/main/scala/stm/Transaction.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/akka-core/src/main/scala/stm/Transaction.scala b/akka-core/src/main/scala/stm/Transaction.scala index 16a3cd0e5f..209c131781 100644 --- a/akka-core/src/main/scala/stm/Transaction.scala +++ b/akka-core/src/main/scala/stm/Transaction.scala @@ -22,6 +22,25 @@ import org.multiverse.stms.alpha.AlphaStm class NoTransactionInScopeException extends RuntimeException class TransactionRetryException(message: String) extends RuntimeException(message) +/** + * FIXDOC: document AtomicTemplate + * AtomicTemplate can be used to create atomic blocks from Java code. + *

+ * User newUser = new AtomicTemplate[User]() {
+ *   User atomic() {
+ *     ... // create user atomically
+ *     return user;
+ *   }
+ * }.execute();
+ * 
+ */ +trait AtomicTemplate[T] { + def atomic: T + def execute: T = Transaction.Local.atomic { + atomic + } +} + object Transaction { val idFactory = new AtomicLong(-1L) From fac08d26d146cb12d4e0d305922013d342abb71b Mon Sep 17 00:00:00 2001 From: rossputin Date: Wed, 14 Apr 2010 20:33:46 +0100 Subject: [PATCH 20/27] update instructions for chat running chat sample --- akka-samples/akka-sample-chat/README | 6 +++--- .../akka-sample-chat/src/main/scala/ChatServer.scala | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/akka-samples/akka-sample-chat/README b/akka-samples/akka-sample-chat/README index 88720d8c55..66e54e3d44 100644 --- a/akka-samples/akka-sample-chat/README +++ b/akka-samples/akka-sample-chat/README @@ -17,10 +17,10 @@ Then to run the sample: - Set 'export AKKA_HOME=. - Run 'sbt console' to start up a REPL (interpreter). 4. In the first REPL you get execute: - - scala> import se.scalablesolutions.akka.sample.chat._ + - scala> import sample.chat._ - scala> ChatService.start -5. In the first REPL you get execute: - - scala> import se.scalablesolutions.akka.sample.chat._ +5. In the second REPL you get execute: + - scala> import sample.chat._ - scala> Runner.run 6. See the chat simulation run. 7. Run it again to see full speed after first initialization. diff --git a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala index 6d4bf9679e..51c4c9f91c 100644 --- a/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala +++ b/akka-samples/akka-sample-chat/src/main/scala/ChatServer.scala @@ -33,10 +33,10 @@ Then to run the sample: - Set 'export AKKA_HOME=. - Run 'sbt console' to start up a REPL (interpreter). 2. In the first REPL you get execute: - - scala> import se.scalablesolutions.akka.sample.chat._ + - scala> import sample.chat._ - scala> ChatService.start -3. In the first REPL you get execute: - - scala> import se.scalablesolutions.akka.sample.chat._ +3. In the second REPL you get execute: + - scala> import sample.chat._ - scala> Runner.run 4. See the chat simulation run. 5. Run it again to see full speed after first initialization. From 379b6e2c8517c8cf4eb950670b5b8b073e3eb2c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Fri, 16 Apr 2010 00:02:38 +0200 Subject: [PATCH 21/27] Updated old scaladoc --- akka-core/src/main/scala/dispatch/Dispatchers.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/akka-core/src/main/scala/dispatch/Dispatchers.scala b/akka-core/src/main/scala/dispatch/Dispatchers.scala index 49d9c624b6..2030d2026e 100644 --- a/akka-core/src/main/scala/dispatch/Dispatchers.scala +++ b/akka-core/src/main/scala/dispatch/Dispatchers.scala @@ -11,7 +11,7 @@ import se.scalablesolutions.akka.actor.Actor *

* Example usage: *

- *   val dispatcher = Dispatchers.newEventBasedThreadPoolDispatcher("name")
+ *   val dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher("name")
  *   dispatcher
  *     .withNewThreadPoolWithBoundedBlockingQueue(100)
  *     .setCorePoolSize(16)
@@ -25,7 +25,7 @@ import se.scalablesolutions.akka.actor.Actor
  * 

* Example usage: *

- *   MessageDispatcher dispatcher = Dispatchers.newEventBasedThreadPoolDispatcher("name");
+ *   MessageDispatcher dispatcher = Dispatchers.newExecutorBasedEventDrivenDispatcher("name");
  *   dispatcher
  *     .withNewThreadPoolWithBoundedBlockingQueue(100)
  *     .setCorePoolSize(16)
@@ -40,9 +40,8 @@ import se.scalablesolutions.akka.actor.Actor
  */
 object Dispatchers {
   object globalExecutorBasedEventDrivenDispatcher extends ExecutorBasedEventDrivenDispatcher("global") {
-    override def register(actor : Actor) = {
-      if (isShutdown)
-        init
+    override def register(actor: Actor) = {
+      if (isShutdown) init
       super.register(actor)
     }
   }

From 5b0f267c403138824895a72b8749398e11b1df69 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= 
Date: Fri, 16 Apr 2010 01:48:53 +0200
Subject: [PATCH 22/27] converted tabs to spaces

---
 akka-core/src/main/scala/actor/Actor.scala    |   4 +-
 .../src/test/scala/PerformanceSpec.scala      |  12 +-
 .../akka/spring/foo/Bar.java                  |   2 +-
 .../akka/spring/foo/StatefulPojo.java         |   2 +-
 .../spring/SupervisorConfigurationTest.java   | 174 +++++++++---------
 5 files changed, 97 insertions(+), 97 deletions(-)

diff --git a/akka-core/src/main/scala/actor/Actor.scala b/akka-core/src/main/scala/actor/Actor.scala
index a78b3d09c8..37a297d5ca 100644
--- a/akka-core/src/main/scala/actor/Actor.scala
+++ b/akka-core/src/main/scala/actor/Actor.scala
@@ -712,7 +712,7 @@ trait Actor extends TransactionManagement with Logging {
    * 

* To be invoked from within the actor itself. */ - protected[this] def spawnRemote[T <: Actor : Manifest](hostname: String, port: Int): T = { + protected[this] def spawnRemote[T <: Actor: Manifest](hostname: String, port: Int): T = { val actor = spawnButDoNotStart[T] actor.makeRemote(hostname, port) actor.start @@ -724,7 +724,7 @@ trait Actor extends TransactionManagement with Logging { *

* To be invoked from within the actor itself. */ - protected[this] def spawnLink[T <: Actor : Manifest] : T = { + protected[this] def spawnLink[T <: Actor: Manifest]: T = { val actor = spawnButDoNotStart[T] try { actor.start diff --git a/akka-core/src/test/scala/PerformanceSpec.scala b/akka-core/src/test/scala/PerformanceSpec.scala index 742a560f06..dd00d5ac3e 100644 --- a/akka-core/src/test/scala/PerformanceSpec.scala +++ b/akka-core/src/test/scala/PerformanceSpec.scala @@ -57,9 +57,9 @@ class PerformanceSpec extends JUnitSuite { } protected def sender : Option[Actor] = replyTo match { - case Some(Left(actor)) => Some(actor) - case _ => None - } + case Some(Left(actor)) => Some(actor) + case _ => None + } def receive = { case MeetingCount(i) => { @@ -104,9 +104,9 @@ class PerformanceSpec extends JUnitSuite { } protected def sender : Option[Actor] = replyTo match { - case Some(Left(actor)) => Some(actor) - case _ => None - } + case Some(Left(actor)) => Some(actor) + case _ => None + } override def receive: PartialFunction[Any, Unit] = { case Meet(from, otherColour) => diff --git a/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/Bar.java b/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/Bar.java index 24e02a3fd6..1b9e67e09c 100644 --- a/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/Bar.java +++ b/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/Bar.java @@ -10,7 +10,7 @@ public class Bar implements IBar { } public void throwsIOException() throws IOException { - throw new IOException("some IO went wrong"); + throw new IOException("some IO went wrong"); } } diff --git a/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/StatefulPojo.java b/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/StatefulPojo.java index 7ee334548c..f2308e194f 100644 --- a/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/StatefulPojo.java +++ b/akka-spring/akka-spring-test-java/src/main/java/se/scalablesolutions/akka/spring/foo/StatefulPojo.java @@ -15,7 +15,7 @@ public class StatefulPojo { @inittransactionalstate public void init() { if (!isInitialized) { - mapState = TransactionalState.newMap(); + mapState = TransactionalState.newMap(); vectorState = TransactionalState.newVector(); refState = TransactionalState.newRef(); isInitialized = true; diff --git a/akka-spring/akka-spring-test-java/src/test/java/se/scalablesolutions/akka/spring/SupervisorConfigurationTest.java b/akka-spring/akka-spring-test-java/src/test/java/se/scalablesolutions/akka/spring/SupervisorConfigurationTest.java index fbbe23d18c..659433cb9f 100644 --- a/akka-spring/akka-spring-test-java/src/test/java/se/scalablesolutions/akka/spring/SupervisorConfigurationTest.java +++ b/akka-spring/akka-spring-test-java/src/test/java/se/scalablesolutions/akka/spring/SupervisorConfigurationTest.java @@ -35,101 +35,101 @@ import se.scalablesolutions.akka.spring.foo.StatefulPojo; */ public class SupervisorConfigurationTest { - private ApplicationContext context = null; + private ApplicationContext context = null; - @Before - public void setUp() { - context = new ClassPathXmlApplicationContext( - "se/scalablesolutions/akka/spring/foo/supervisor-config.xml"); - } + @Before + public void setUp() { + context = new ClassPathXmlApplicationContext( + "se/scalablesolutions/akka/spring/foo/supervisor-config.xml"); + } - @Test - public void testSupervision() { - // get ActiveObjectConfigurator bean from spring context - ActiveObjectConfigurator myConfigurator = (ActiveObjectConfigurator) context - .getBean("supervision1"); - // get ActiveObjects - Foo foo = myConfigurator.getInstance(Foo.class); - assertNotNull(foo); - IBar bar = myConfigurator.getInstance(IBar.class); - assertNotNull(bar); - MyPojo pojo = myConfigurator.getInstance(MyPojo.class); - assertNotNull(pojo); - } + @Test + public void testSupervision() { + // get ActiveObjectConfigurator bean from spring context + ActiveObjectConfigurator myConfigurator = (ActiveObjectConfigurator) context + .getBean("supervision1"); + // get ActiveObjects + Foo foo = myConfigurator.getInstance(Foo.class); + assertNotNull(foo); + IBar bar = myConfigurator.getInstance(IBar.class); + assertNotNull(bar); + MyPojo pojo = myConfigurator.getInstance(MyPojo.class); + assertNotNull(pojo); + } - @Test - public void testTransactionalState() { - ActiveObjectConfigurator conf = (ActiveObjectConfigurator) context - .getBean("supervision2"); - StatefulPojo stateful = conf.getInstance(StatefulPojo.class); - stateful.setMapState("testTransactionalState", "some map state"); - stateful.setVectorState("some vector state"); - stateful.setRefState("some ref state"); - assertEquals("some map state", stateful - .getMapState("testTransactionalState")); - assertEquals("some vector state", stateful.getVectorState()); - assertEquals("some ref state", stateful.getRefState()); - } + @Test + public void testTransactionalState() { + ActiveObjectConfigurator conf = (ActiveObjectConfigurator) context + .getBean("supervision2"); + StatefulPojo stateful = conf.getInstance(StatefulPojo.class); + stateful.setMapState("testTransactionalState", "some map state"); + stateful.setVectorState("some vector state"); + stateful.setRefState("some ref state"); + assertEquals("some map state", stateful + .getMapState("testTransactionalState")); + assertEquals("some vector state", stateful.getVectorState()); + assertEquals("some ref state", stateful.getRefState()); + } - @Test - public void testInitTransactionalState() { - StatefulPojo stateful = ActiveObject.newInstance(StatefulPojo.class, - 1000, true); - assertTrue("should be inititalized", stateful.isInitialized()); - } + @Test + public void testInitTransactionalState() { + StatefulPojo stateful = ActiveObject.newInstance(StatefulPojo.class, + 1000, true); + assertTrue("should be inititalized", stateful.isInitialized()); + } - @Test - public void testSupervisionWithDispatcher() { - ActiveObjectConfigurator myConfigurator = (ActiveObjectConfigurator) context - .getBean("supervision-with-dispatcher"); - // get ActiveObjects - Foo foo = myConfigurator.getInstance(Foo.class); - assertNotNull(foo); - // TODO how to check dispatcher? - } - - @Test - public void testRemoteActiveObject() { - new Thread(new Runnable() { - public void run() { - RemoteNode.start(); - } - }).start(); - try { - Thread.currentThread().sleep(1000); - } catch (Exception e) { - } - Foo instance = ActiveObject.newRemoteInstance(Foo.class, 2000, "localhost", 9999); - System.out.println(instance.foo()); - } + @Test + public void testSupervisionWithDispatcher() { + ActiveObjectConfigurator myConfigurator = (ActiveObjectConfigurator) context + .getBean("supervision-with-dispatcher"); + // get ActiveObjects + Foo foo = myConfigurator.getInstance(Foo.class); + assertNotNull(foo); + // TODO how to check dispatcher? + } + + @Test + public void testRemoteActiveObject() { + new Thread(new Runnable() { + public void run() { + RemoteNode.start(); + } + }).start(); + try { + Thread.currentThread().sleep(1000); + } catch (Exception e) { + } + Foo instance = ActiveObject.newRemoteInstance(Foo.class, 2000, "localhost", 9999); + System.out.println(instance.foo()); + } - @Test - public void testSupervisedRemoteActiveObject() { - new Thread(new Runnable() { - public void run() { - RemoteNode.start(); - } - }).start(); - try { - Thread.currentThread().sleep(1000); - } catch (Exception e) { - } + @Test + public void testSupervisedRemoteActiveObject() { + new Thread(new Runnable() { + public void run() { + RemoteNode.start(); + } + }).start(); + try { + Thread.currentThread().sleep(1000); + } catch (Exception e) { + } - ActiveObjectConfigurator conf = new ActiveObjectConfigurator(); - conf.configure( - new RestartStrategy(new AllForOne(), 3, 10000, new Class[] { Exception.class }), - new Component[] { - new Component( - Foo.class, - new LifeCycle(new Permanent()), - 10000, - new RemoteAddress("localhost", 9999)) - }).supervise(); + ActiveObjectConfigurator conf = new ActiveObjectConfigurator(); + conf.configure( + new RestartStrategy(new AllForOne(), 3, 10000, new Class[] { Exception.class }), + new Component[] { + new Component( + Foo.class, + new LifeCycle(new Permanent()), + 10000, + new RemoteAddress("localhost", 9999)) + }).supervise(); - Foo instance = conf.getInstance(Foo.class); - assertEquals("foo", instance.foo()); - } + Foo instance = conf.getInstance(Foo.class); + assertEquals("foo", instance.foo()); + } - + } From 3ac7acccd7bc6f1411113e6acb58056eb5c68411 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Wed, 14 Apr 2010 09:07:20 +0800 Subject: [PATCH 23/27] tests for TransactionalRef --- .../src/test/scala/TransactionalRefSpec.scala | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 akka-core/src/test/scala/TransactionalRefSpec.scala diff --git a/akka-core/src/test/scala/TransactionalRefSpec.scala b/akka-core/src/test/scala/TransactionalRefSpec.scala new file mode 100644 index 0000000000..b4e9c0e67a --- /dev/null +++ b/akka-core/src/test/scala/TransactionalRefSpec.scala @@ -0,0 +1,68 @@ +package se.scalablesolutions.akka.stm + +import org.scalatest.Spec +import org.scalatest.matchers.ShouldMatchers +import org.scalatest.junit.JUnitRunner +import org.junit.runner.RunWith + +@RunWith(classOf[JUnitRunner]) +class TransactionalRefSpec extends Spec with ShouldMatchers { + + describe("A TransactionalRef") { + import Transaction.Local._ + + it("should optionally accept an initial value") { + val emptyRef = Ref[Int] + val empty = atomic { emptyRef.get } + + empty should be(None) + + val ref = Ref(3) + val value = atomic { ref.get.get } + + value should be(3) + } + + it("should be settable using swap") { + val ref = Ref[Int] + + atomic { ref.swap(3) } + + val value = atomic { ref.get.get } + + value should be(3) + } + + it("should be changeable using alter") { + val ref = Ref(0) + + def increment = atomic { + ref alter (_ + 1) + } + + increment + increment + increment + + val value = atomic { ref.get.get } + + value should be(3) + } + + it("should not be changeable using alter if no value has been set") { + val ref = Ref[Int] + + def increment = atomic { + ref alter (_ + 1) + } + + evaluating { increment } should produce [RuntimeException] + } + + it("should be able to be mapped") (pending) + it("should be able to be used in a 'foreach' for comprehension") (pending) + it("should be able to be used in a 'map' for comprehension") (pending) + it("should be able to be used in a 'flatMap' for comprehension") (pending) + it("should be able to be used in a 'filter' for comprehension") (pending) + } +} From 86ee7d8b0b079ac950676e94126827d51f813734 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Wed, 14 Apr 2010 09:27:45 +0800 Subject: [PATCH 24/27] tests for TransactionalRef in for comprehensions --- .../src/test/scala/TransactionalRefSpec.scala | 79 +++++++++++++++++-- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/akka-core/src/test/scala/TransactionalRefSpec.scala b/akka-core/src/test/scala/TransactionalRefSpec.scala index b4e9c0e67a..07c36ebdcf 100644 --- a/akka-core/src/test/scala/TransactionalRefSpec.scala +++ b/akka-core/src/test/scala/TransactionalRefSpec.scala @@ -59,10 +59,79 @@ class TransactionalRefSpec extends Spec with ShouldMatchers { evaluating { increment } should produce [RuntimeException] } - it("should be able to be mapped") (pending) - it("should be able to be used in a 'foreach' for comprehension") (pending) - it("should be able to be used in a 'map' for comprehension") (pending) - it("should be able to be used in a 'flatMap' for comprehension") (pending) - it("should be able to be used in a 'filter' for comprehension") (pending) + it("should be able to be mapped") { + val ref1 = Ref(1) + + val ref2 = atomic { + ref1 map (_ + 1) + } + + val value1 = atomic { ref1.get.get } + val value2 = atomic { ref2.get.get } + + value1 should be(1) + value2 should be(2) + } + + it("should be able to be used in a 'foreach' for comprehension") { + val ref = Ref(3) + + var result = 0 + + atomic { + for (value <- ref) { + result += value + } + } + + result should be(3) + } + + it("should be able to be used in a 'map' for comprehension") { + val ref1 = Ref(1) + + val ref2 = atomic { + for (value <- ref1) yield value + 2 + } + + val value2 = atomic { ref2.get.get } + + value2 should be(3) + } + + it("should be able to be used in a 'flatMap' for comprehension") { + val ref1 = Ref(1) + val ref2 = Ref(2) + + val ref3 = atomic { + for { + value1 <- ref1 + value2 <- ref2 + } yield value1 + value2 + } + + val value3 = atomic { ref3.get.get } + + value3 should be(3) + } + + it("should be able to be used in a 'filter' for comprehension") { + val ref1 = Ref(1) + + val refLess2 = atomic { + for (value <- ref1 if value < 2) yield value + } + + val optLess2 = atomic { refLess2.get } + + val refGreater2 = atomic { + for (value <- ref1 if value > 2) yield value + } + + val optGreater2 = atomic { refGreater2.get } + + optLess2 should be(Some(1)) + optGreater2 should be(None) + } } } From 5ab81358361cc656dcd987c716041a16752e9643 Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Wed, 14 Apr 2010 09:08:50 +0800 Subject: [PATCH 25/27] updating TransactionalRef to be properly monadic --- .../main/scala/stm/TransactionalState.scala | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/akka-core/src/main/scala/stm/TransactionalState.scala b/akka-core/src/main/scala/stm/TransactionalState.scala index e84beaa4f0..9bf4859ee5 100644 --- a/akka-core/src/main/scala/stm/TransactionalState.scala +++ b/akka-core/src/main/scala/stm/TransactionalState.scala @@ -62,6 +62,8 @@ trait Committable { * @author Jonas Bonér */ object Ref { + type Ref[T] = TransactionalRef[T] + def apply[T]() = new Ref[T] def apply[T](initialValue: T) = new Ref[T](Some(initialValue)) @@ -75,7 +77,7 @@ object Ref { object TransactionalRef { /** - * An implicit conversion that converts an Option to an Iterable value. + * An implicit conversion that converts a TransactionalRef to an Iterable value. */ implicit def ref2Iterable[T](ref: TransactionalRef[T]): Iterable[T] = ref.toList @@ -84,14 +86,6 @@ object TransactionalRef { def apply[T](initialValue: T) = new TransactionalRef[T](Some(initialValue)) } -/** - * Implements a transactional managed reference. - * Alias to TransactionalRef. - * - * @author Jonas Bonér - */ -class Ref[T](initialOpt: Option[T] = None) extends TransactionalRef[T](initialOpt) - /** * Implements a transactional managed reference. * Alias to Ref. @@ -99,6 +93,8 @@ class Ref[T](initialOpt: Option[T] = None) extends TransactionalRef[T](initialOp * @author Jonas Bonér */ class TransactionalRef[T](initialOpt: Option[T] = None) extends Transactional { + self => + import org.multiverse.api.ThreadLocalTransaction._ implicit val txInitName = "TransactionalRef:Init" @@ -149,24 +145,36 @@ class TransactionalRef[T](initialOpt: Option[T] = None) extends Transactional { ref.isNull } - def map[B](f: T => B): Option[B] = { + def map[B](f: T => B): TransactionalRef[B] = { ensureIsInTransaction - if (isEmpty) None else Some(f(ref.get)) + if (isEmpty) TransactionalRef[B] else TransactionalRef(f(ref.get)) } - def flatMap[B](f: T => Option[B]): Option[B] = { + def flatMap[B](f: T => TransactionalRef[B]): TransactionalRef[B] = { ensureIsInTransaction - if (isEmpty) None else f(ref.get) + if (isEmpty) TransactionalRef[B] else f(ref.get) } - def filter(p: T => Boolean): Option[T] = { + def filter(p: T => Boolean): TransactionalRef[T] = { ensureIsInTransaction - if (isEmpty || p(ref.get)) Some(ref.get) else None + if (isDefined && p(ref.get)) TransactionalRef(ref.get) else TransactionalRef[T] } - def foreach(f: T => Unit) { + /** + * Necessary to keep from being implicitly converted to Iterable in for comprehensions. + */ + def withFilter(p: T => Boolean): WithFilter = new WithFilter(p) + + class WithFilter(p: T => Boolean) { + def map[B](f: T => B): TransactionalRef[B] = self filter p map f + def flatMap[B](f: T => TransactionalRef[B]): TransactionalRef[B] = self filter p flatMap f + def foreach[U](f: T => U): Unit = self filter p foreach f + def withFilter(q: T => Boolean): WithFilter = new WithFilter(x => p(x) && q(x)) + } + + def foreach[U](f: T => U): Unit = { ensureIsInTransaction - if (!isEmpty) f(ref.get) + if (isDefined) f(ref.get) } def elements: Iterator[T] = { From a596fbbd61a3db1606d335cb4a48421a79ebf607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Fri, 16 Apr 2010 09:23:24 +0200 Subject: [PATCH 26/27] Added Cassandra logging dependencies to the compile jars --- project/build/AkkaProject.scala | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala index b0466d5ec8..960d380a05 100644 --- a/project/build/AkkaProject.scala +++ b/project/build/AkkaProject.scala @@ -23,7 +23,7 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { lazy val deployPath = info.projectPath / "deploy" lazy val distPath = info.projectPath / "dist" - override def compileOptions = super.compileOptions ++ + override def compileOptions = super.compileOptions ++ Seq("-deprecation", "-Xmigration", "-Xcheckinit", "-Xstrict-warnings", "-Xwarninit", "-encoding", "utf8").map(x => CompileOption(x)) override def javaCompileOptions = JavaCompileOption("-Xlint:unchecked") :: super.javaCompileOptions.toList @@ -236,13 +236,14 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) { class AkkaCassandraProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) { val cassandra = "org.apache.cassandra" % "cassandra" % CASSANDRA_VERSION % "compile" + val slf4j = "org.slf4j" % "slf4j-api" % "1.5.8" % "compile" + val slf4j_log4j = "org.slf4j" % "slf4j-log4j12" % "1.5.8" % "compile" + val log4j = "log4j" % "log4j" % "1.2.15" % "compile" + // testing val high_scale = "org.apache.cassandra" % "high-scale-lib" % CASSANDRA_VERSION % "test" val cassandra_clhm = "org.apache.cassandra" % "clhm-production" % CASSANDRA_VERSION % "test" val commons_coll = "commons-collections" % "commons-collections" % "3.2.1" % "test" val google_coll = "com.google.collections" % "google-collections" % "1.0" % "test" - val slf4j = "org.slf4j" % "slf4j-api" % "1.5.8" % "test" - val slf4j_log4j = "org.slf4j" % "slf4j-log4j12" % "1.5.8" % "test" - val log4j = "log4j" % "log4j" % "1.2.15" % "test" override def testOptions = TestFilter((name: String) => name.endsWith("Test")) :: Nil } From 584de097ec95bb2eadad815e764071c64deaf55f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bon=C3=A9r?= Date: Fri, 16 Apr 2010 14:36:51 +0200 Subject: [PATCH 27/27] added sbt plugin file --- project/plugins/Plugins.scala | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 project/plugins/Plugins.scala diff --git a/project/plugins/Plugins.scala b/project/plugins/Plugins.scala new file mode 100644 index 0000000000..a929827cc6 --- /dev/null +++ b/project/plugins/Plugins.scala @@ -0,0 +1,5 @@ +import sbt._ + +class Plugins(info: ProjectInfo) extends PluginDefinition(info) { +// val surefire = "bryanjswift" % "sbt-surefire-reporting" % "0.0.3-SNAPSHOT" +} \ No newline at end of file