second iteration of STM done, simple tests work now

This commit is contained in:
Jonas Boner 2009-04-09 15:49:42 +02:00
parent 2639d14e1a
commit cd1ef83e49
40 changed files with 4102 additions and 3215 deletions

View file

@ -14,6 +14,14 @@ import java.lang.annotation.Annotation
sealed class ActiveObjectException(msg: String) extends RuntimeException(msg)
class ActiveObjectInvocationTimeoutException(msg: String) extends ActiveObjectException(msg)
object Annotation {
import se.scalablesolutions.akka.annotation._
val transactional = classOf[transactional]
val oneway = classOf[oneway]
val immutable = classOf[immutable]
val state = classOf[state]
}
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
@ -70,11 +78,7 @@ object ActiveObject {
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
class ActiveObjectProxy(val intf: Class[_], val target: Class[_], val timeout: Int) extends InvocationHandler {
val transactional = classOf[se.scalablesolutions.akka.annotation.transactional]
val oneway = classOf[se.scalablesolutions.akka.annotation.oneway]
val immutable = classOf[se.scalablesolutions.akka.annotation.immutable]
val state= classOf[se.scalablesolutions.akka.annotation.state]
import ActiveObject.threadBoundTx
private[this] var activeTx: Option[Transaction] = None
private var targetInstance: AnyRef = _
@ -86,58 +90,39 @@ class ActiveObjectProxy(val intf: Class[_], val target: Class[_], val timeout: I
}
}
private[this] val dispatcher = new GenericServer {
override def body: PartialFunction[Any, Unit] = {
case invocation: Invocation =>
val tx = invocation.tx
ActiveObject.threadBoundTx.set(tx)
try {
reply(ErrRef(invocation.invoke, tx))
} catch {
case e: InvocationTargetException =>
val te = e.getTargetException
te.printStackTrace
reply(ErrRef({ throw te }, tx))
case e =>
e.printStackTrace
reply(ErrRef({ throw e }, tx))
}
case 'exit => exit; reply()
case unexpected => throw new ActiveObjectException("Unexpected message to actor proxy: " + unexpected)
}
}
private[kernel] val server = new GenericServerContainer(target.getName, () => dispatcher)
private[kernel] val server = new GenericServerContainer(target.getName, () => new Dispatcher(target.getName))
server.setTimeout(timeout)
def invoke(proxy: AnyRef, m: Method, args: Array[AnyRef]): AnyRef = {
if (m.isAnnotationPresent(transactional)) {
if (m.isAnnotationPresent(Annotation.transactional)) {
// FIXME: check if we are already in a transaction if so NEST (set parent)
val newTx = new Transaction
newTx.begin(server)
ActiveObject.threadBoundTx.set(Some(newTx))
threadBoundTx.set(Some(newTx))
}
val cflowTx = ActiveObject.threadBoundTx.get
// println("========== invoking: " + m.getName)
// println("========== cflowTx: " + cflowTx)
// println("========== activeTx: " + activeTx)
val cflowTx = threadBoundTx.get
activeTx match {
case Some(tx) =>
if (cflowTx.isDefined && cflowTx.get != tx) {
// new tx in scope; try to commit
tx.commit(server)
threadBoundTx.set(None)
activeTx = None
}
case None =>
if (cflowTx.isDefined) activeTx = Some(cflowTx.get)
if (cflowTx.isDefined) {
val currentTx = cflowTx.get
currentTx.join(server)
activeTx = Some(currentTx)
}
}
activeTx = ActiveObject.threadBoundTx.get
activeTx = threadBoundTx.get
invoke(Invocation(m, args, targetInstance, activeTx))
}
private def invoke(invocation: Invocation): AnyRef = {
val result: AnyRef =
if (invocation.method.isAnnotationPresent(oneway)) server ! invocation
if (invocation.method.isAnnotationPresent(Annotation.oneway)) server ! invocation
else {
val result: ErrRef[AnyRef] =
server !!! (invocation, {
@ -153,6 +138,7 @@ class ActiveObjectProxy(val intf: Class[_], val target: Class[_], val timeout: I
throw e
}
}
// FIXME: clear threadBoundTx on successful commit
if (activeTx.isDefined) activeTx.get.precommit(server)
result
}
@ -160,45 +146,76 @@ class ActiveObjectProxy(val intf: Class[_], val target: Class[_], val timeout: I
private def rollback(tx: Option[Transaction]) = tx match {
case None => {} // no tx; nothing to do
case Some(tx) =>
println("================ ROLLING BACK")
tx.rollback(server)
ActiveObject.threadBoundTx.set(Some(tx))
threadBoundTx.set(Some(tx))
}
private def getStateList(targetInstance: AnyRef): List[State[_,_]] = {
require(targetInstance != null)
import se.scalablesolutions.akka.kernel.configuration.ConfigurationException
val states = for {
field <- target.getDeclaredFields
if field.isAnnotationPresent(state)
state = field.get(targetInstance)
val states: List[State[_,_]] = for {
field <- target.getDeclaredFields.toArray.toList
if field.isAnnotationPresent(Annotation.state)
state = {
field.setAccessible(true)
field.get(targetInstance)
}
if state != null
} yield {
if (!state.isInstanceOf[State[_, _]]) throw new ConfigurationException("Fields annotated with [@state] needs to to be a subtype of [se.scalablesolutions.akka.kernel.State[K, V]]")
state
state.asInstanceOf[State[_,_]]
}
states
// if (fields.size > 1) throw new ConfigurationException("Stateful active object can only have one single field '@Inject TransientObjectState state' defined")
}
}
/**
* Generic GenericServer managing Invocation dispatch, transaction and error management.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
private[kernel] class Dispatcher(val targetName: String) extends GenericServer {
override def body: PartialFunction[Any, Unit] = {
case invocation: Invocation =>
val tx = invocation.tx
ActiveObject.threadBoundTx.set(tx)
try {
reply(ErrRef(invocation.invoke, tx))
} catch {
case e: InvocationTargetException =>
val ref = ErrRef(tx); ref() = throw e.getTargetException; reply(ref)
case e =>
val ref = ErrRef(tx); ref() = throw e; reply(ref)
}
case 'exit =>
exit; reply()
case unexpected =>
throw new ActiveObjectException("Unexpected message [" + unexpected + "] to [" + this + "] from [" + sender + "]")
}
override def toString(): String = "GenericServer[" + targetName + "]"
}
/**
* Represents a snapshot of the current invocation.
*
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
case class Invocation(val method: Method,
val args: Array[AnyRef],
val target: AnyRef,
val tx: Option[Transaction]) {
private[kernel] case class Invocation(val method: Method,
val args: Array[AnyRef],
val target: AnyRef,
val tx: Option[Transaction]) {
method.setAccessible(true)
def invoke: AnyRef = method.invoke(target, args:_*)
def invoke: AnyRef = synchronized {
method.invoke(target, args:_*)
}
override def toString: String =
override def toString: String = synchronized {
"Invocation [method: " + method.getName + ", args: " + argsToString(args) + ", target: " + target + "]"
override def hashCode(): Int = {
}
override def hashCode(): Int = synchronized {
var result = HashCode.SEED
result = HashCode.hash(result, method)
result = HashCode.hash(result, args)
@ -206,7 +223,7 @@ case class Invocation(val method: Method,
result
}
override def equals(that: Any): Boolean = {
override def equals(that: Any): Boolean = synchronized {
that != null &&
that.isInstanceOf[Invocation] &&
that.asInstanceOf[Invocation].method == method &&
@ -214,14 +231,13 @@ case class Invocation(val method: Method,
isEqual(that.asInstanceOf[Invocation].args, args)
}
private def isEqual(a1: Array[Object], a2: Array[Object]): Boolean =
private[this] def isEqual(a1: Array[Object], a2: Array[Object]): Boolean =
(a1 == null && a2 == null) ||
(a1 != null &&
a2 != null &&
a1.size == a2.size &&
a1.zip(a2).find(t => t._1 == t._2).isDefined)
private def argsToString(array: Array[Object]): String = synchronized {
private[this] def argsToString(array: Array[Object]): String =
array.foldLeft("(")(_ + " " + _) + ")"
}
}

View file

@ -267,8 +267,7 @@ class GenericServerContainer(
private[kernel] def terminate(reason: AnyRef, shutdownTime: Int) = lock.withReadLock {
if (shutdownTime > 0) {
log.debug("Waiting [%s milliseconds for the server to shut down before killing it.", shutdownTime)
// server !? (shutdownTime, Shutdown(reason)) match {
server !? Shutdown(reason) match {
server !? (shutdownTime, Shutdown(reason)) match {
case Some('success) => log.debug("Server [%s] has been shut down cleanly.", id)
case None => log.warning("Server [%s] was **not able** to complete shutdown cleanly within its configured shutdown time [%s]", id, shutdownTime)
}
@ -292,5 +291,7 @@ class GenericServerContainer(
private[kernel] def swapServer(newServer: GenericServer) = lock.withWriteLock {
server = newServer
}
override def toString(): String = "GenericServerContainer[" + server + "]"
}

View file

@ -4,10 +4,10 @@
package se.scalablesolutions.akka.kernel
import org.apache.zookeeper.jmx.ManagedUtil
import org.apache.zookeeper.server.persistence.FileTxnSnapLog
import org.apache.zookeeper.server.ServerConfig
import org.apache.zookeeper.server.NIOServerCnxn
//import org.apache.zookeeper.jmx.ManagedUtil
//import org.apache.zookeeper.server.persistence.FileTxnSnapLog
//import org.apache.zookeeper.server.ServerConfig
//import org.apache.zookeeper.server.NIOServerCnxn
import voldemort.client.{SocketStoreClientFactory, StoreClient, StoreClientFactory}
import voldemort.server.{VoldemortConfig, VoldemortServer}

View file

@ -13,7 +13,7 @@ trait Transactional {
private[kernel] def rollback
}
sealed trait State[K, V] {
sealed trait State[K, V] extends Transactional {
def put(key: K, value: V)
def remove(key: K)
def get(key: K): V
@ -23,7 +23,10 @@ sealed trait State[K, V] {
def clear
}
sealed class TransientState[K, V] extends State[K, V] with Transactional {
/**
* Not thread-safe, but should only be using from within an Actor, e.g. one single thread at a time.
*/
sealed class TransientState[K, V] extends State[K, V] {
private[kernel] var state = new HashTrie[K, V]
private[kernel] var snapshot = state
@ -32,6 +35,7 @@ sealed class TransientState[K, V] extends State[K, V] with Transactional {
}
private[kernel] override def commit = {
snapshot = state
}
private[kernel] override def rollback = {
@ -60,6 +64,9 @@ sealed class TransientState[K, V] extends State[K, V] with Transactional {
final class TransientStringState extends TransientState[String, String]
final class TransientObjectState extends TransientState[String, AnyRef]
/**
* Not thread-safe, but should only be using from within an Actor, e.g. one single thread at a time.
*/
trait UnitOfWork[K, V] extends State[K, V] with Transactional {
this: TransientState[K, V] =>
private[kernel] val changeSet = new HashMap[K, V]

View file

@ -93,7 +93,7 @@ abstract class SupervisorFactory extends Logging {
val supervisor = create(restartStrategy)
supervisor.start
supervisor !? Configure(config, this) match {
case 'success => log.debug("Supervisor successfully configured")
case 'configSuccess => log.debug("Supervisor successfully configured")
case _ => log.error("Supervisor could not be configured")
}
supervisor
@ -148,7 +148,7 @@ class Supervisor(faultHandler: FaultHandlingStrategy) extends Actor with Logging
case Configure(config, factory) =>
log.debug("Configuring supervisor:%s ", this)
configure(config, factory)
reply('success)
reply('configSuccess)
case Start =>
state.serverContainers.foreach { serverContainer =>
@ -229,20 +229,21 @@ abstract class FaultHandlingStrategy(val maxNrOfRetries: Int, val withinTimeRang
private[kernel] def restart(serverContainer: GenericServerContainer, reason: AnyRef, state: SupervisorState) = {
preRestart(serverContainer)
serverContainer.lock.withWriteLock {
// TODO: this is the place to fail-over all pending messages in the failing actor's mailbox, if possible to get a hold of them
// e.g. something like 'serverContainer.getServer.getPendingMessages.map(newServer ! _)'
self.unlink(serverContainer.getServer)
serverContainer.lifeCycle match {
case None => throw new IllegalStateException("Server [" + serverContainer.id + "] does not have a life-cycle defined.")
case Some(LifeCycle(scope, shutdownTime)) =>
case None =>
throw new IllegalStateException("Server [" + serverContainer.id + "] does not have a life-cycle defined.")
case Some(LifeCycle(scope, shutdownTime)) => {
serverContainer.terminate(reason, shutdownTime)
scope match {
case Permanent =>
case Permanent => {
log.debug("Restarting server [%s] configured as PERMANENT.", serverContainer.id)
serverContainer.reconfigure(reason, supervisor.spawnLink(serverContainer), state.supervisor)
}
case Temporary =>
if (reason == 'normal) {
@ -253,6 +254,7 @@ abstract class FaultHandlingStrategy(val maxNrOfRetries: Int, val withinTimeRang
case Transient =>
log.info("Server [%s] configured as TRANSIENT will not be restarted.", serverContainer.id)
}
}
}
}
postRestart(serverContainer)

View file

@ -36,21 +36,25 @@ class Transaction extends Logging {
log.debug("Creating a new transaction [%s]", id)
private[this] var parent: Option[Transaction] = None
private[this] var participants = new HashMap[GenericServerContainer, GenericServer]
private[this] var participants: List[GenericServerContainer] = Nil
private[this] var precommitted: List[GenericServerContainer] = Nil
@volatile private[this] var status: TransactionStatus = TransactionStatus.New
def begin(server: GenericServerContainer) = synchronized {
println("===== begin 1 " + server)
if (status == TransactionStatus.Aborted) throw new IllegalStateException("Can't begin ABORTED transaction")
if (status == TransactionStatus.Completed) throw new IllegalStateException("Can't begin COMPLETED transaction")
if (status == TransactionStatus.New) log.debug("Actor [%s] is starting NEW transaction", server)
else log.debug("Actor [%s] is participating in transaction", server)
if (server.state.isDefined) server.state.get.begin
println("===== begin 2 " + server)
server.states.foreach(_.begin)
participants ::= server
status = TransactionStatus.Active
}
def precommit(server: GenericServerContainer) = synchronized {
if (status == TransactionStatus.Active) {
println("===== precommit " + server)
log.debug("Pre-committing transaction for actor [%s]", server)
precommitted ::= server
}
@ -58,10 +62,11 @@ class Transaction extends Logging {
def commit(server: GenericServerContainer) = synchronized {
if (status == TransactionStatus.Active) {
println("===== commit " + server)
log.debug("Committing transaction for actor [%s]", server)
val haveAllPreCommitted =
if (participants.size == precommitted.size) {{
for (server <- participants.keys) yield {
for (server <- participants) yield {
if (precommitted.exists(_.id == server.id)) true
else false
}}.exists(_ == false)
@ -73,14 +78,18 @@ class Transaction extends Logging {
def rollback(server: GenericServerContainer) = synchronized {
ensureIsActiveOrAborted
log.debug("Actor [%s] has initiated transaction rollback, rolling back [%s]" , server, participants.keys)
participants.foreach(entry => {
val (server, backup) = entry
if (server.state.isDefined) server.state.get.rollback
})
println("===== rollback " + server)
log.debug("Actor [%s] has initiated transaction rollback, rolling back [%s]" , server, participants)
participants.foreach(_.states.foreach(_.rollback))
status = TransactionStatus.Aborted
}
def join(server: GenericServerContainer) = synchronized {
println("===== joining " + server)
server.states.foreach(_.begin)
participants ::= server
}
private def ensureIsActive = if (status != TransactionStatus.Active)
throw new IllegalStateException("Expected ACTIVE transaction - current status [" + status + "]")

View file

@ -222,7 +222,7 @@ class Vector[+T] private (val length: Int, shift: Int, root: Array[AnyRef], tail
back
}
override def flatMap[A](f: (T)=>Iterable[A]) = {
override def flatMap[A](f: (T)=>Iterable[A]): Vector[A] = {
var back = new Vector[A]
var i = 0
@ -234,7 +234,7 @@ class Vector[+T] private (val length: Int, shift: Int, root: Array[AnyRef], tail
back
}
override def map[A](f: (T)=>A) = {
override def map[A](f: (T)=>A): Vector[A] = {
var back = new Vector[A]
var i = 0

View file

@ -1,188 +0,0 @@
/**
* Copyright (C) 2009 Scalable Solutions.
*/
package se.scalablesolutions.akka.kernel
import org.scalatest.Spec
import org.scalatest.matchers.ShouldMatchers
import org.scalatest.junit.JUnit3Suite
import se.scalablesolutions.akka.annotation.{oneway, transactional, stateful}
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
object ActiveObjectSpec {
var messageLog = ""
}
class ActiveObjectSpec extends Spec with ShouldMatchers {
describe("An ActiveObject") {
it("(with default supervisor) should dispatch method calls normally") {
val foo = ActiveObject.newInstance[Foo](classOf[Foo], new FooImpl, 1000)
val result = foo.foo("foo ")
ActiveObjectSpec.messageLog += result
foo.bar("bar ")
ActiveObjectSpec.messageLog += "before_bar "
Thread.sleep(500)
ActiveObjectSpec.messageLog should equal ("foo return_foo before_bar bar ")
}
it("should not rollback state for a stateful server in case of success") {
val stateful = ActiveObject.newInstance[Stateful](classOf[Stateful], new StatefulImpl, 1000)
stateful.success("new state")
stateful.state should equal ("new state")
}
it("should rollback state for a stateful server in case of failure") {
val stateful = ActiveObject.newInstance[Stateful](classOf[Stateful], new StatefulImpl, 1000)
val failer = ActiveObject.newInstance[Failer](classOf[Failer], new FailerImpl, 1000)
stateful.failure("new state", failer)
stateful.state should equal ("nil")
}
}
}
trait Foo {
def foo(msg: String): String
@transactional def fooInTx(msg: String): String
@oneway def bar(msg: String)
def longRunning
def throwsException
}
class FooImpl extends Foo {
val bar: Bar = new BarImpl
def foo(msg: String): String = {
ActiveObjectSpec.messageLog += msg
"return_foo "
}
def fooInTx(msg: String): String = {
ActiveObjectSpec.messageLog += msg
"return_foo "
}
def bar(msg: String) = bar.bar(msg)
def longRunning = Thread.sleep(10000)
def throwsException = error("expected")
}
trait Bar {
@oneway def bar(msg: String)
}
class BarImpl extends Bar {
def bar(msg: String) = {
Thread.sleep(100)
ActiveObjectSpec.messageLog += msg
}
}
trait Stateful {
@transactional def success(msg: String)
@transactional def failure(msg: String, failer: Failer)
def state: String
}
@stateful
class StatefulImpl extends Stateful {
var state: String = "nil"
def success(msg: String) = state = msg
def failure(msg: String, failer: Failer) = {
state = msg
failer.fail
}
}
trait Failer {
def fail
}
class FailerImpl extends Failer {
def fail = throw new RuntimeException("expected")
}
// @Test { val groups=Array("unit") }
// def testCreateGenericServerBasedComponentUsingCustomSupervisorConfiguration = {
// val proxy = new ActiveObjectProxy(new FooImpl, 1000)
// val supervisor =
// ActiveObject.supervise(
// RestartStrategy(AllForOne, 3, 100),
// Component(
// proxy,
// LifeCycle(Permanent, 100))
// :: Nil)
// val foo = ActiveObject.newInstance[Foo](classOf[Foo], proxy)
// val result = foo.foo("foo ")
// messageLog += result
// foo.bar("bar ")
// messageLog += "before_bar "
// Thread.sleep(500)
// assert(messageLog === "foo return_foo before_bar bar ")
// supervisor ! Stop
// }
// @Test { val groups=Array("unit") }
// def testCreateTwoGenericServerBasedComponentUsingCustomSupervisorConfiguration = {
// val fooProxy = new ActiveObjectProxy(new FooImpl, 1000)
// val barProxy = new ActiveObjectProxy(new BarImpl, 1000)
// val supervisor =
// ActiveObject.supervise(
// RestartStrategy(AllForOne, 3, 100),
// Component(
// fooProxy,
// LifeCycle(Permanent, 100)) ::
// Component(
// barProxy,
// LifeCycle(Permanent, 100))
// :: Nil)
// val foo = ActiveObject.newInstance[Foo](classOf[Foo], fooProxy)
// val bar = ActiveObject.newInstance[Bar](classOf[Bar], barProxy)
// val result = foo.foo("foo ")
// messageLog += result
// bar.bar("bar ")
// messageLog += "before_bar "
// Thread.sleep(500)
// assert(messageLog === "foo return_foo before_bar bar ")
// supervisor ! Stop
// }
// @Test { val groups=Array("unit") }
// def testCreateGenericServerBasedComponentUsingDefaultSupervisorAndForcedTimeout = {
// val foo = ActiveObject.newInstance[Foo](classOf[Foo], new FooImpl, 1000)
// intercept(classOf[ActiveObjectInvocationTimeoutException]) {
// foo.longRunning
// }
// assert(true === true)
// }
// @Test { val groups=Array("unit") }
// def testCreateGenericServerBasedComponentUsingDefaultSupervisorAndForcedException = {
// val foo = ActiveObject.newInstance[Foo](classOf[Foo], new FooImpl, 10000)
// intercept(classOf[RuntimeException]) {
// foo.throwsException
// }
// assert(true === true)
// }
// }

View file

@ -10,9 +10,14 @@ import org.scalatest._
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
class AllSuite extends SuperSuite(
List(
new ActiveObjectSpec,
new RestManagerSpec
new SupervisorSpec,
new SupervisorStateSpec,
new GenericServerSpec,
new GenericServerContainerSpec
// new ActiveObjectSpec,
// new RestManagerSpec
)
)

View file

@ -0,0 +1,212 @@
/**
* Copyright (C) 2009 Scalable Solutions.
*/
package se.scalablesolutions.akka.kernel
import scala.actors._
import scala.actors.Actor._
import org.scalatest._
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
class GenericServerContainerSpec extends Suite {
var inner: GenericServerContainerActor = null
var server: GenericServerContainer = null
def createProxy(f: () => GenericServer) = { val server = new GenericServerContainer("server", f); server.setTimeout(100); server }
def setup = {
inner = new GenericServerContainerActor
server = createProxy(() => inner)
server.newServer
server.start
}
def testInit = {
setup
server.init("testInit")
Thread.sleep(100)
expect("initializing: testInit") {
inner.log
}
}
def testTerminateWithReason = {
setup
server.terminate("testTerminateWithReason", 100)
Thread.sleep(100)
expect("terminating: testTerminateWithReason") {
inner.log
}
}
def test_bang_1 = {
setup
server ! OneWay
Thread.sleep(100)
expect("got a oneway") {
inner.log
}
}
def test_bang_2 = {
setup
server ! Ping
Thread.sleep(100)
expect("got a ping") {
inner.log
}
}
def test_bangbangbang = {
setup
expect("pong") {
(server !!! Ping).getOrElse("nil")
}
expect("got a ping") {
inner.log
}
}
def test_bangquestion = {
setup
expect("pong") {
val res: String = server !? Ping
res
}
expect("got a ping") {
inner.log
}
}
def test_bangbangbang_Timeout1 = {
setup
expect("pong") {
(server !!! Ping).getOrElse("nil")
}
expect("got a ping") {
inner.log
}
}
def test_bangbangbang_Timeout2 = {
setup
expect("error handler") {
server !!! (OneWay, "error handler")
}
expect("got a oneway") {
inner.log
}
}
def test_bangbangbang_GetFutureTimeout1 = {
setup
val future = server !! Ping
future.receiveWithin(100) match {
case None => fail("timed out") // timed out
case Some(reply) =>
expect("got a ping") {
inner.log
}
assert("pong" === reply)
}
}
def test_bangbangbang_GetFutureTimeout2 = {
setup
val future = server !! OneWay
future.receiveWithin(100) match {
case None =>
expect("got a oneway") {
inner.log
}
case Some(reply) =>
fail("expected a timeout, got Some(reply)")
}
}
def testHotSwap = {
setup
// using base
expect("pong") {
(server !!! Ping).getOrElse("nil")
}
// hotswapping
server.hotswap(Some({
case Ping => reply("hotswapped pong")
}))
expect("hotswapped pong") {
(server !!! Ping).getOrElse("nil")
}
}
def testDoubleHotSwap = {
setup
// using base
expect("pong") {
(server !!! Ping).getOrElse("nil")
}
// hotswapping
server.hotswap(Some({
case Ping => reply("hotswapped pong")
}))
expect("hotswapped pong") {
(server !!! Ping).getOrElse("nil")
}
// hotswapping again
server.hotswap(Some({
case Ping => reply("hotswapped pong again")
}))
expect("hotswapped pong again") {
(server !!! Ping).getOrElse("nil")
}
}
def testHotSwapReturnToBase = {
setup
// using base
expect("pong") {
(server !!! Ping).getOrElse("nil")
}
// hotswapping
server.hotswap(Some({
case Ping => reply("hotswapped pong")
}))
expect("hotswapped pong") {
(server !!! Ping).getOrElse("nil")
}
// restoring original base
server.hotswap(None)
expect("pong") {
(server !!! Ping).getOrElse("nil")
}
}
}
class GenericServerContainerActor extends GenericServer {
var log = ""
override def body: PartialFunction[Any, Unit] = {
case Ping =>
log = "got a ping"
reply("pong")
case OneWay =>
log = "got a oneway"
}
override def init(config: AnyRef) = log = "initializing: " + config
override def shutdown(reason: AnyRef) = log = "terminating: " + reason
}

View file

@ -0,0 +1,37 @@
/**
* Copyright (C) 2009 Scalable Solutions.
*/
package se.scalablesolutions.akka.kernel
import org.scalatest._
import scala.actors.Actor._
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
class GenericServerSpec extends Suite {
def testSendRegularMessage = {
val server = new MyGenericServerActor
server.start
server !? Ping match {
case reply: String =>
assert("got a ping" === server.log)
assert("pong" === reply)
case _ => fail()
}
}
}
class MyGenericServerActor extends GenericServer {
var log: String = ""
override def body: PartialFunction[Any, Unit] = {
case Ping =>
log = "got a ping"
reply("pong")
}
}

View file

@ -0,0 +1,12 @@
/**
* Copyright (C) 2009 Scalable Solutions.
*/
package se.scalablesolutions.akka.kernel
sealed abstract class TestMessage
case object Ping extends TestMessage
case object Pong extends TestMessage
case object OneWay extends TestMessage
case object Die extends TestMessage
case object NotifySupervisorExit extends TestMessage

View file

@ -1,42 +0,0 @@
/**
* Copyright (C) 2009 Scalable Solutions.
*/
package se.scalablesolutions.akka.kernel
import org.scalatest.Spec
import org.scalatest.matchers.ShouldMatchers
import javax.ws.rs.{Produces, Path, GET}
//import com.sun.net.httpserver.HttpServer;
//import com.sun.ws.rest.api.client.Client;
//import com.sun.ws.rest.api.client.ClientResponse;
//import com.sun.ws.rest.api.client.ResourceProxy;
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
class RestManagerSpec extends Spec with ShouldMatchers {
describe("A RestManager") {
it("should be able to start and stop") {
val threadSelector = Kernel.startJersey
/* val cc = new DefaultClientConfig
val c = Client.create(cc)
val resource = c.proxy("http://localhost:9998/")
val hello = resource.get(classOf[HelloWorldResource])
val msg = hello.getMessage
println("=============: " + msg)
*/ threadSelector.stopEndpoint
}
}
}
@Path("/helloworld")
class HelloWorldResource {
@GET
@Produces(Array("text/plain"))
def getMessage = "Hello World"
}

View file

@ -0,0 +1,430 @@
/**
* Copyright (C) 2009 Scalable Solutions.
*/
package se.scalablesolutions.akka.kernel
import scala.actors._
import scala.actors.Actor._
import scala.collection.Map
import scala.collection.mutable.HashMap
import org.scalatest._
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
class SupervisorSpec extends Suite {
var messageLog: String = ""
val pingpong1 = new GenericServerContainer("pingpong1", () => new PingPong1Actor)
val pingpong2 = new GenericServerContainer("pingpong2", () => new PingPong2Actor)
val pingpong3 = new GenericServerContainer("pingpong3", () => new PingPong3Actor)
pingpong1.setTimeout(100)
pingpong2.setTimeout(100)
pingpong3.setTimeout(100)
def testStartServer = {
messageLog = ""
val sup = getSingleActorAllForOneSupervisor
sup ! Start
expect("pong") {
(pingpong1 !!! Ping).getOrElse("nil")
}
}
def testGetServer = {
messageLog = ""
val sup = getSingleActorAllForOneSupervisor
sup ! Start
val server = sup.getServerOrElse("pingpong1", throw new RuntimeException("server not found"))
assert(server.isInstanceOf[GenericServerContainer])
assert(server === pingpong1)
}
def testGetServerOrFail = {
messageLog = ""
val sup = getSingleActorAllForOneSupervisor
sup ! Start
intercept(classOf[RuntimeException]) {
sup.getServerOrElse("wrong_name", throw new RuntimeException("server not found"))
}
}
def testKillSingleActorOneForOne = {
messageLog = ""
val sup = getSingleActorOneForOneSupervisor
sup ! Start
intercept(classOf[RuntimeException]) {
pingpong1 !!! (Die, throw new RuntimeException("TIME OUT"))
}
Thread.sleep(100)
expect("oneforone") {
messageLog
}
}
def testCallKillCallSingleActorOneForOne = {
messageLog = ""
val sup = getSingleActorOneForOneSupervisor
sup ! Start
expect("pong") {
(pingpong1 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("ping") {
messageLog
}
intercept(classOf[RuntimeException]) {
pingpong1 !!! (Die, throw new RuntimeException("TIME OUT"))
}
Thread.sleep(100)
expect("pingoneforone") {
messageLog
}
expect("pong") {
(pingpong1 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pingoneforoneping") {
messageLog
}
}
def testKillSingleActorAllForOne = {
messageLog = ""
val sup = getSingleActorAllForOneSupervisor
sup ! Start
intercept(classOf[RuntimeException]) {
pingpong1 !!! (Die, throw new RuntimeException("TIME OUT"))
}
Thread.sleep(100)
expect("allforone") {
messageLog
}
}
def testCallKillCallSingleActorAllForOne = {
messageLog = ""
val sup = getSingleActorAllForOneSupervisor
sup ! Start
expect("pong") {
(pingpong1 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("ping") {
messageLog
}
intercept(classOf[RuntimeException]) {
pingpong1 !!! (Die, throw new RuntimeException("TIME OUT"))
}
Thread.sleep(100)
expect("pingallforone") {
messageLog
}
expect("pong") {
(pingpong1 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pingallforoneping") {
messageLog
}
}
def testKillMultipleActorsOneForOne = {
messageLog = ""
val sup = getMultipleActorsOneForOneConf
sup ! Start
intercept(classOf[RuntimeException]) {
pingpong3 !!! (Die, throw new RuntimeException("TIME OUT"))
}
Thread.sleep(100)
expect("oneforone") {
messageLog
}
}
def tesCallKillCallMultipleActorsOneForOne = {
messageLog = ""
val sup = getMultipleActorsOneForOneConf
sup ! Start
expect("pong") {
(pingpong1 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pong") {
(pingpong2 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pong") {
(pingpong3 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pingpingping") {
messageLog
}
intercept(classOf[RuntimeException]) {
pingpong2 !!! (Die, throw new RuntimeException("TIME OUT"))
}
Thread.sleep(100)
expect("pingpingpingoneforone") {
messageLog
}
expect("pong") {
(pingpong1 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pong") {
(pingpong2 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pong") {
(pingpong3 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pingpingpingoneforonepingpingping") {
messageLog
}
}
def testKillMultipleActorsAllForOne = {
messageLog = ""
val sup = getMultipleActorsAllForOneConf
sup ! Start
intercept(classOf[RuntimeException]) {
pingpong2 !!! (Die, throw new RuntimeException("TIME OUT"))
}
Thread.sleep(100)
expect("allforoneallforoneallforone") {
messageLog
}
}
def tesCallKillCallMultipleActorsAllForOne = {
messageLog = ""
val sup = getMultipleActorsAllForOneConf
sup ! Start
expect("pong") {
(pingpong1 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pong") {
(pingpong2 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pong") {
(pingpong3 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pingpingping") {
messageLog
}
intercept(classOf[RuntimeException]) {
pingpong2 !!! (Die, throw new RuntimeException("TIME OUT"))
}
Thread.sleep(100)
expect("pingpingpingallforoneallforoneallforone") {
messageLog
}
expect("pong") {
(pingpong1 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pong") {
(pingpong2 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pong") {
(pingpong3 !!! Ping).getOrElse("nil")
}
Thread.sleep(100)
expect("pingpingpingallforoneallforoneallforonepingpingping") {
messageLog
}
}
def testTerminateFirstLevelActorAllForOne = {
messageLog = ""
val sup = getNestedSupervisorsAllForOneConf
sup ! Start
intercept(classOf[RuntimeException]) {
pingpong1 !!! (Die, throw new RuntimeException("TIME OUT"))
}
Thread.sleep(100)
expect("allforoneallforoneallforone") {
messageLog
}
}
// =============================================
// Creat some supervisors with different configurations
def getSingleActorAllForOneSupervisor: Supervisor = {
// Create an abstract SupervisorContainer that works for all implementations
// of the different Actors (Services).
//
// Then create a concrete container in which we mix in support for the specific
// implementation of the Actors we want to use.
object factory extends TestSupervisorFactory {
override def getSupervisorConfig: SupervisorConfig = {
SupervisorConfig(
RestartStrategy(AllForOne, 3, 100),
Worker(
pingpong1,
LifeCycle(Permanent, 100))
:: Nil)
}
}
factory.newSupervisor
}
def getSingleActorOneForOneSupervisor: Supervisor = {
object factory extends TestSupervisorFactory {
override def getSupervisorConfig: SupervisorConfig = {
SupervisorConfig(
RestartStrategy(OneForOne, 3, 100),
Worker(
pingpong1,
LifeCycle(Permanent, 100))
:: Nil)
}
}
factory.newSupervisor
}
def getMultipleActorsAllForOneConf: Supervisor = {
object factory extends TestSupervisorFactory {
override def getSupervisorConfig: SupervisorConfig = {
SupervisorConfig(
RestartStrategy(AllForOne, 3, 100),
Worker(
pingpong1,
LifeCycle(Permanent, 100))
::
Worker(
pingpong2,
LifeCycle(Permanent, 100))
::
Worker(
pingpong3,
LifeCycle(Permanent, 100))
:: Nil)
}
}
factory.newSupervisor
}
def getMultipleActorsOneForOneConf: Supervisor = {
object factory extends TestSupervisorFactory {
override def getSupervisorConfig: SupervisorConfig = {
SupervisorConfig(
RestartStrategy(OneForOne, 3, 100),
Worker(
pingpong1,
LifeCycle(Permanent, 100))
::
Worker(
pingpong2,
LifeCycle(Permanent, 100))
::
Worker(
pingpong3,
LifeCycle(Permanent, 100))
:: Nil)
}
}
factory.newSupervisor
}
def getNestedSupervisorsAllForOneConf: Supervisor = {
object factory extends TestSupervisorFactory {
override def getSupervisorConfig: SupervisorConfig = {
SupervisorConfig(
RestartStrategy(AllForOne, 3, 100),
Worker(
pingpong1,
LifeCycle(Permanent, 100))
::
SupervisorConfig(
RestartStrategy(AllForOne, 3, 100),
Worker(
pingpong2,
LifeCycle(Permanent, 100))
::
Worker(
pingpong3,
LifeCycle(Permanent, 100))
:: Nil)
:: Nil)
}
}
factory.newSupervisor
}
class PingPong1Actor extends GenericServer {
override def body: PartialFunction[Any, Unit] = {
case Ping =>
messageLog += "ping"
reply("pong")
case Die =>
throw new RuntimeException("Recieved Die message")
}
}
class PingPong2Actor extends GenericServer {
override def body: PartialFunction[Any, Unit] = {
case Ping =>
messageLog += "ping"
reply("pong")
case Die =>
throw new RuntimeException("Recieved Die message")
}
}
class PingPong3Actor extends GenericServer {
override def body: PartialFunction[Any, Unit] = {
case Ping =>
messageLog += "ping"
reply("pong")
case Die =>
throw new RuntimeException("Recieved Die message")
}
}
// =============================================
class TestAllForOneStrategy(maxNrOfRetries: Int, withinTimeRange: Int) extends AllForOneStrategy(maxNrOfRetries, withinTimeRange) {
override def postRestart(serverContainer: GenericServerContainer) = {
messageLog += "allforone"
}
}
class TestOneForOneStrategy(maxNrOfRetries: Int, withinTimeRange: Int) extends OneForOneStrategy(maxNrOfRetries, withinTimeRange) {
override def postRestart(serverContainer: GenericServerContainer) = {
messageLog += "oneforone"
}
}
abstract class TestSupervisorFactory extends SupervisorFactory {
override def create(strategy: RestartStrategy): Supervisor = strategy match {
case RestartStrategy(scheme, maxNrOfRetries, timeRange) =>
scheme match {
case AllForOne => new Supervisor(new TestAllForOneStrategy(maxNrOfRetries, timeRange))
case OneForOne => new Supervisor(new TestOneForOneStrategy(maxNrOfRetries, timeRange))
}
}
}
}

View file

@ -0,0 +1,92 @@
/**
* Copyright (C) 2009 Scalable Solutions.
*/
package se.scalablesolutions.akka.kernel
import org.scalatest._
import scala.actors.Actor._
/**
* @author <a href="http://jonasboner.com">Jonas Bon&#233;r</a>
*/
class SupervisorStateSpec extends Suite {
val dummyActor = new GenericServer { override def body: PartialFunction[Any, Unit] = { case _ => }}
val newDummyActor = () => dummyActor
var state: SupervisorState = _
var proxy: GenericServerContainer = _
var supervisor: Supervisor = _
def setup = {
proxy = new GenericServerContainer("server1", newDummyActor)
object factory extends SupervisorFactory {
override def getSupervisorConfig: SupervisorConfig = {
SupervisorConfig(
RestartStrategy(AllForOne, 3, 100),
Worker(
proxy,
LifeCycle(Permanent, 100))
:: Nil)
}
}
supervisor = factory.newSupervisor
state = new SupervisorState(supervisor, new AllForOneStrategy(3, 100))
}
def testAddServer = {
setup
state.addServerContainer(proxy)
state.getServerContainer("server1") match {
case None => fail("should have returned server")
case Some(server) =>
assert(server != null)
assert(server.isInstanceOf[GenericServerContainer])
assert(proxy === server)
}
}
def testGetServer = {
setup
state.addServerContainer(proxy)
state.getServerContainer("server1") match {
case None => fail("should have returned server")
case Some(server) =>
assert(server != null)
assert(server.isInstanceOf[GenericServerContainer])
assert(proxy === server)
}
}
def testRemoveServer = {
setup
state.addServerContainer(proxy)
state.removeServerContainer("server1")
state.getServerContainer("server1") match {
case Some(_) => fail("should have returned None")
case None =>
}
state.getServerContainer("dummyActor") match {
case Some(_) => fail("should have returned None")
case None =>
}
}
def testGetNonExistingServerBySymbol = {
setup
state.getServerContainer("server2") match {
case Some(_) => fail("should have returned None")
case None =>
}
}
def testGetNonExistingServerByActor = {
setup
state.getServerContainer("dummyActor") match {
case Some(_) => fail("should have returned None")
case None =>
}
}
}