From d97df97a936e63b5089ce9b0c79cab0ddd2f723e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20Bon=C3=A9r?=
Date: Thu, 8 Apr 2010 17:02:01 +0200
Subject: [PATCH 01/41] Added JTA module, monadic and higher-order functional
API
---
.../scala/AtomikosTransactionService.scala | 36 +++
.../src/main/scala/TransactionContext.scala | 181 ++++++++++++
.../src/main/scala/TransactionProtocol.scala | 258 ++++++++++++++++++
project/build/AkkaProject.scala | 17 +-
4 files changed, 489 insertions(+), 3 deletions(-)
create mode 100644 akka-jta/src/main/scala/AtomikosTransactionService.scala
create mode 100644 akka-jta/src/main/scala/TransactionContext.scala
create mode 100644 akka-jta/src/main/scala/TransactionProtocol.scala
diff --git a/akka-jta/src/main/scala/AtomikosTransactionService.scala b/akka-jta/src/main/scala/AtomikosTransactionService.scala
new file mode 100644
index 0000000000..d688564686
--- /dev/null
+++ b/akka-jta/src/main/scala/AtomikosTransactionService.scala
@@ -0,0 +1,36 @@
+/**
+ * Copyright (C) 2009-2010 Scalable Solutions AB
+ */
+
+package se.scalablesolutions.akka.jta
+
+import javax.transaction.{TransactionManager, SystemException}
+
+import com.atomikos.icatch.jta.{J2eeTransactionManager, J2eeUserTransaction}
+import com.atomikos.icatch.config.{TSInitInfo, UserTransactionService, UserTransactionServiceImp}
+
+/**
+ * Atomikos implementation of the transaction service trait.
+ *
+ * @author Jonas Bonér
+ */
+object AtomikosTransactionService extends TransactionService with TransactionProtocol {
+
+ // FIXME: make configurable
+ val JTA_TRANSACTION_TIMEOUT = 60
+ private val txService: UserTransactionService = new UserTransactionServiceImp
+ private val info: TSInitInfo = txService.createTSInitInfo
+
+ val transactionManager =
+ try {
+ txService.init(info)
+ val tm: TransactionManager = new J2eeTransactionManager
+ tm.setTransactionTimeout(JTA_TRANSACTION_TIMEOUT)
+ tm
+ } catch {
+ case e => throw new SystemException("Could not create a new Atomikos J2EE Transaction Manager, due to: " + e.toString)
+ }
+
+ // TODO: gracefully shutdown of the TM
+ //txService.shutdown(false)
+}
diff --git a/akka-jta/src/main/scala/TransactionContext.scala b/akka-jta/src/main/scala/TransactionContext.scala
new file mode 100644
index 0000000000..3ed562eeb5
--- /dev/null
+++ b/akka-jta/src/main/scala/TransactionContext.scala
@@ -0,0 +1,181 @@
+/**
+ * Copyright (C) 2009-2010 Scalable Solutions AB
+ */
+
+package se.scalablesolutions.akka.jta
+
+import javax.transaction.{Transaction, Status, TransactionManager}
+
+import se.scalablesolutions.akka.util.Logging
+
+/**
+ * JTA Transaction service.
+ *
+ * @author Jonas Bonér
+ */
+trait TransactionService {
+ def transactionManager: TransactionManager
+}
+
+/**
+ * Base monad for the transaction monad implementations.
+ *
+ * @author Jonas Bonér
+ */
+trait TransactionMonad {
+
+ // -----------------------------
+ // Monadic definitions
+ // -----------------------------
+
+ def map[T](f: TransactionMonad => T): T
+ def flatMap[T](f: TransactionMonad => T): T
+ def foreach(f: TransactionMonad => Unit): Unit
+ def filter(f: TransactionMonad => Boolean): TransactionMonad =
+ if (f(this)) this else TransactionContext.NoOpTransactionMonad
+
+ // -----------------------------
+ // JTA Transaction definitions
+ // -----------------------------
+
+ /**
+ * Returns the current Transaction.
+ */
+ def getTransaction: Transaction = TransactionContext.getTransactionManager.getTransaction
+
+ /**
+ * Marks the current transaction as doomed.
+ */
+ def setRollbackOnly = TransactionContext.setRollbackOnly
+
+ /**
+ * Marks the current transaction as doomed.
+ */
+ def doom = TransactionContext.setRollbackOnly
+
+ /**
+ * Checks if the current transaction is doomed.
+ */
+ def isRollbackOnly = TransactionContext.isRollbackOnly
+
+ /**
+ * Checks that the current transaction is NOT doomed.
+ */
+ def isNotDoomed = !TransactionContext.isRollbackOnly
+}
+
+/**
+ * Manages a thread-local stack of TransactionContexts.
+ *
+ * Choose TransactionService implementation by implicit definition of the implementation of choice,
+ * e.g. implicit val txService = TransactionServices.AtomikosTransactionService.
+ *
+ * Example usage 1:
+ *
+ * for {
+ * ctx <- TransactionContext.Required
+ * entity <- updatedEntities
+ * if !ctx.isRollbackOnly
+ * } {
+ * // transactional stuff
+ * ...
+ * }
+ *
+ * Example usage 2:
+ *
+ * val users = for {
+ * ctx <- TransactionContext.Required
+ * name <- userNames
+ * } yield {
+ * // transactional stuff
+ * ...
+ * }
+ *
+ *
+ * @author Jonas Bonér
+ */
+object TransactionContext extends TransactionProtocol with Logging {
+ // FIXME: make configurable
+ private implicit val defaultTransactionService = AtomikosTransactionService
+
+ private[TransactionContext] val stack = new scala.util.DynamicVariable(new TransactionContext)
+
+ object Required extends TransactionMonad {
+ def map[T](f: TransactionMonad => T): T = withTxRequired { f(this) }
+ def flatMap[T](f: TransactionMonad => T): T = withTxRequired { f(this) }
+ def foreach(f: TransactionMonad => Unit): Unit = withTxRequired { f(this) }
+ }
+
+ object RequiresNew extends TransactionMonad {
+ def map[T](f: TransactionMonad => T): T = withTxRequiresNew { f(this) }
+ def flatMap[T](f: TransactionMonad => T): T = withTxRequiresNew { f(this) }
+ def foreach(f: TransactionMonad => Unit): Unit = withTxRequiresNew { f(this) }
+ }
+
+ object Supports extends TransactionMonad {
+ def map[T](f: TransactionMonad => T): T = withTxSupports { f(this) }
+ def flatMap[T](f: TransactionMonad => T): T = withTxSupports { f(this) }
+ def foreach(f: TransactionMonad => Unit): Unit = withTxSupports { f(this) }
+ }
+
+ object Mandatory extends TransactionMonad {
+ def map[T](f: TransactionMonad => T): T = withTxMandatory { f(this) }
+ def flatMap[T](f: TransactionMonad => T): T = withTxMandatory { f(this) }
+ def foreach(f: TransactionMonad => Unit): Unit = withTxMandatory { f(this) }
+ }
+
+ object Never extends TransactionMonad {
+ def map[T](f: TransactionMonad => T): T = withTxNever { f(this) }
+ def flatMap[T](f: TransactionMonad => T): T = withTxNever { f(this) }
+ def foreach(f: TransactionMonad => Unit): Unit = withTxNever { f(this) }
+ }
+
+ object NoOpTransactionMonad extends TransactionMonad {
+ def map[T](f: TransactionMonad => T): T = f(this)
+ def flatMap[T](f: TransactionMonad => T): T = f(this)
+ def foreach(f: TransactionMonad => Unit): Unit = f(this)
+ override def filter(f: TransactionMonad => Boolean): TransactionMonad = this
+ }
+
+ private[jta] def setRollbackOnly = current.setRollbackOnly
+
+ private[jta] def isRollbackOnly = current.isRollbackOnly
+
+ private[jta] def getTransactionManager: TransactionManager = current.getTransactionManager
+
+ private[jta] def getTransaction: Transaction = current.getTransactionManager.getTransaction
+
+ private[this] def current = stack.value
+
+ /**
+ * Continues with the invocation defined in 'body' with the brand new context define in 'newCtx', the old
+ * one is put on the stack and will automatically come back in scope when the method exits.
+ *
+ * Suspends and resumes the current JTA transaction.
+ */
+ private[jta] def withNewContext[T](body: => T): T = {
+ val suspendedTx: Option[Transaction] =
+ if (isInExistingTransaction(getTransactionManager)) {
+ log.debug("Suspending TX")
+ Some(getTransactionManager.suspend)
+ } else None
+ val result = stack.withValue(new TransactionContext) { body }
+ if (suspendedTx.isDefined) {
+ log.debug("Resuming TX")
+ getTransactionManager.resume(suspendedTx.get)
+ }
+ result
+ }
+}
+
+/**
+ * Transaction context, holds the EntityManager and the TransactionManager.
+ *
+ * @author Jonas Bonér
+ */
+class TransactionContext(private implicit val transactionService: TransactionService) {
+ val tm: TransactionManager = transactionService.transactionManager
+ private def setRollbackOnly = tm.setRollbackOnly
+ private def isRollbackOnly: Boolean = tm.getStatus == Status.STATUS_MARKED_ROLLBACK
+ private def getTransactionManager: TransactionManager = tm
+}
diff --git a/akka-jta/src/main/scala/TransactionProtocol.scala b/akka-jta/src/main/scala/TransactionProtocol.scala
new file mode 100644
index 0000000000..4a4c263eb1
--- /dev/null
+++ b/akka-jta/src/main/scala/TransactionProtocol.scala
@@ -0,0 +1,258 @@
+/**
+ * Copyright (C) 2009-2010 Scalable Solutions AB
+ */
+
+package se.scalablesolutions.akka.jta
+
+import se.scalablesolutions.akka.util.Logging
+
+import javax.naming.{NamingException, Context, InitialContext}
+import javax.transaction.{
+ Transaction,
+ UserTransaction,
+ TransactionManager,
+ Status,
+ RollbackException,
+ SystemException,
+ TransactionRequiredException
+}
+
+/**
+ *
+ * Trait that implements a JTA transaction service that obeys the transaction semantics defined
+ * in the transaction attribute types for the transacted methods according to the EJB 3 draft specification.
+ * The aspect handles UserTransaction, TransactionManager instance variable injection thru @javax.ejb.Inject
+ * (name subject to change as per EJB 3 spec) and method transaction levels thru @javax.ejb.TransactionAttribute.
+ *
+ *
+ *
+ * This trait should be inherited to implement the getTransactionManager() method that should return a concrete
+ * javax.transaction.TransactionManager implementation (from JNDI lookup etc).
+ *
+ *
+ *
Transaction attribute semantics
+ * (From http://www.kevinboone.com/ejb-transactions.html)
+ *
+ *
+ *
Required
+ * 'Required' is probably the best choice (at least initially) for an EJB method that will need to be transactional. In this case, if the method's caller is already part of a transaction, then the EJB method does not create a new transaction, but continues in the same transaction as its caller. If the caller is not in a transaction, then a new transaction is created for the EJB method. If something happens in the EJB that means that a rollback is required, then the extent of the rollback will include everything done in the EJB method, whatever the condition of the caller. If the caller was in a transaction, then everything done by the caller will be rolled back as well. Thus the 'required' attribute ensures that any work done by the EJB will be rolled back if necessary, and if the caller requires a rollback that too will be rolled back.
+ *
+ *
+ *
RequiresNew
+ * 'RequiresNew' will be appropriate if you want to ensure that the EJB method is rolled back if necessary, but you don't want the rollback to propogate back to the caller. This attribute results in the creation of a new transaction for the method, regardless of the transactional state of the caller. If the caller was operating in a transaction, then its transaction is suspended until the EJB method completes. Because a new transaction is always created, there may be a slight performance penalty if this attribute is over-used.
+ *
+ *
+ *
Mandatory
+ * With the 'mandatory' attribute, the EJB method will not even start unless its caller is in a transaction. It will throw a TransactionRequiredException instead. If the method does start, then it will become part of the transaction of the caller. So if the EJB method signals a failure, the caller will be rolled back as well as the EJB.
+ *
+ *
+ *
Supports
+ * With this attribute, the EJB method does not care about the transactional context of its caller. If the caller is part of a transaction, then the EJB method will be part of the same transaction. If the EJB method fails, the transaction will roll back. If the caller is not part of a transaction, then the EJB method will still operate, but a failure will not cause anything to roll back. 'Supports' is probably the attribute that leads to the fastest method call (as there is no transactional overhead), but it can lead to unpredicatable results. If you want a method to be isolated from transactions, that is, to have no effect on the transaction of its caller, then use 'NotSupported' instead.
+ *
+ *
+ *
NotSupported
+ * With the 'NotSupported' attribute, the EJB method will never take part in a transaction. If the caller is part of a transaction, then the caller's transaction is suspended. If the EJB method fails, there will be no effect on the caller's transaction, and no rollback will occur. Use this method if you want to ensure that the EJB method will not cause a rollback in its caller. This is appropriate if, for example, the method does something non-essential, such as logging a message. It would not be helpful if the failure of this operation caused a transaction rollback.
+ *
+ *
+ *
Never
+ * The 'NotSupported'' attribute will ensure that the EJB method is never called by a transactional caller. Any attempt to do so will result in a RemoteException being thrown. This attribute is probably less useful than `NotSupported', in that NotSupported will assure that the caller's transaction is never affected by the EJB method (just as `Never' does), but will allow a call from a transactional caller if necessary.
+ *
+ *
+ * @author Jonas Bonér
+ */
+trait TransactionProtocol extends Logging {
+
+ /**
+ * Join JTA transaction. Can be overriden by concrete transaction service implementation
+ * to hook into other transaction services.
+ *
+ * Here is an example on how to integrate with JPA EntityManager.
+ *
+ *
+ * override def joinTransaction = {
+ * val em = TransactionContext.getEntityManager
+ * val tm = TransactionContext.getTransactionManager
+ * val closeAtTxCompletion: Boolean)
+ * tm.getTransaction.registerSynchronization(new javax.transaction.Synchronization() {
+ * def beforeCompletion = {
+ * try {
+ * val status = tm.getStatus
+ * if (status != Status.STATUS_ROLLEDBACK &&
+ * status != Status.STATUS_ROLLING_BACK &&
+ * status != Status.STATUS_MARKED_ROLLBACK) {
+ * log.debug("Flushing EntityManager...")
+ * em.flush // flush EntityManager on success
+ * }
+ * } catch {
+ * case e: javax.transaction.SystemException => throw new RuntimeException(e)
+ * }
+ * }
+ *
+ * def afterCompletion(status: Int) = {
+ * val status = tm.getStatus
+ * if (closeAtTxCompletion) em.close
+ * if (status == Status.STATUS_ROLLEDBACK ||
+ * status == Status.STATUS_ROLLING_BACK ||
+ * status == Status.STATUS_MARKED_ROLLBACK) {
+ * em.close
+ * }
+ * }
+ * })
+ * em.joinTransaction // join JTA transaction
+ * }
+ */
+ def joinTransaction: Unit = {}
+
+ /**
+ * Handle exception. Can be overriden by concrete transaction service implementation.
+ *
+ * Here is an example on how to handle JPA exceptions.
+ *
+ *
+ * def handleException(tm: TransactionManager, e: Exception) = {
+ * if (isInExistingTransaction(tm)) {
+ * // Do not roll back in case of NoResultException or NonUniqueResultException
+ * if (!e.isInstanceOf[NoResultException] &&
+ * !e.isInstanceOf[NonUniqueResultException]) {
+ * log.debug("Setting TX to ROLLBACK_ONLY, due to: " + e)
+ * tm.setRollbackOnly
+ * }
+ * }
+ * throw e
+ * }
+ *
+ */
+ def handleException(tm: TransactionManager, e: Exception) = {
+ tm.setRollbackOnly
+ throw e
+ }
+
+ /**
+ * Wraps body in a transaction with REQUIRED semantics.
+ *
+ * Creates a new transaction if no transaction is active in scope, else joins the outer transaction.
+ */
+ def withTxRequired[T](body: => T): T = {
+ val tm = TransactionContext.getTransactionManager
+ if (!isInExistingTransaction(tm)) {
+ tm.begin
+ try {
+ joinTransaction
+ body
+ } catch {
+ case e: Exception => handleException(tm, e)
+ } finally {
+ commitOrRollBack(tm)
+ }
+ } else body
+ }
+
+ /**
+ * Wraps body in a transaction with REQUIRES_NEW semantics.
+ *
+ * Suspends existing transaction, starts a new transaction, invokes body,
+ * commits or rollbacks new transaction, finally resumes previous transaction.
+ */
+ def withTxRequiresNew[T](body: => T): T = TransactionContext.withNewContext {
+ val tm = TransactionContext.getTransactionManager
+ tm.begin
+ try {
+ joinTransaction
+ body
+ } catch {
+ case e: Exception => handleException(tm, e)
+ } finally {
+ commitOrRollBack(tm)
+ }
+ }
+
+ /**
+ * Wraps body in a transaction with NOT_SUPPORTED semantics.
+ *
+ * Suspends existing transaction, invokes body, resumes transaction.
+ */
+ def withTxNotSupported[T](body: => T): T = TransactionContext.withNewContext {
+ body
+ }
+
+ /**
+ * Wraps body in a transaction with SUPPORTS semantics.
+ *
+ * Basicalla a No-op.
+ */
+ def withTxSupports[T](body: => T): T = {
+ // attach to current if exists else skip -> do nothing
+ body
+ }
+
+ /**
+ * Wraps body in a transaction with MANDATORY semantics.
+ *
+ * Throws a TransactionRequiredException if there is no transaction active in scope.
+ */
+ def withTxMandatory[T](body: => T): T = {
+ if (!isInExistingTransaction(TransactionContext.getTransactionManager))
+ throw new TransactionRequiredException("No active TX at method with TX type set to MANDATORY")
+ body
+ }
+
+ /**
+ * Wraps body in a transaction with NEVER semantics.
+ *
+ * Throws a SystemException in case of an existing transaction in scope.
+ */
+ def withTxNever[T](body: => T): T = {
+ if (isInExistingTransaction(TransactionContext.getTransactionManager))
+ throw new SystemException("Detected active TX at method with TX type set to NEVER")
+ body
+ }
+
+ protected def commitOrRollBack(tm: TransactionManager) = {
+ if (isInExistingTransaction(tm)) {
+ if (isRollbackOnly(tm)) {
+ log.debug("Rolling back TX marked as ROLLBACK_ONLY")
+ tm.rollback
+ } else {
+ log.debug("Committing TX")
+ tm.commit
+ }
+ }
+ }
+
+ // ---------------------------
+ // Helper methods
+ // ---------------------------
+
+ /**
+ * Checks if a transaction is an existing transaction.
+ *
+ * @param tm the transaction manager
+ * @return boolean
+ */
+ protected def isInExistingTransaction(tm: TransactionManager): Boolean =
+ tm.getStatus != Status.STATUS_NO_TRANSACTION
+
+ /**
+ * Checks if current transaction is set to rollback only.
+ *
+ * @param tm the transaction manager
+ * @return boolean
+ */
+ protected def isRollbackOnly(tm: TransactionManager): Boolean =
+ tm.getStatus == Status.STATUS_MARKED_ROLLBACK
+
+ /**
+ * A ThreadLocal variable where to store suspended TX and enable pay as you go
+ * before advice - after advice data sharing in a specific case of requiresNew TX
+ */
+ private val suspendedTx = new ThreadLocal[Transaction] {
+ override def initialValue = null
+ }
+
+ private def storeInThreadLocal(tx: Transaction) = suspendedTx.set(tx)
+
+ private def fetchFromThreadLocal: Option[Transaction] = {
+ if (suspendedTx != null && suspendedTx.get() != null) Some(suspendedTx.get.asInstanceOf[Transaction])
+ else None
+ }
+}
diff --git a/project/build/AkkaProject.scala b/project/build/AkkaProject.scala
index b7c02135fc..1104535609 100644
--- a/project/build/AkkaProject.scala
+++ b/project/build/AkkaProject.scala
@@ -80,9 +80,10 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) {
val codehaus_snapshots = "Codehaus Snapshots" at "http://snapshots.repository.codehaus.org"
val jboss = "jBoss" at "http://repository.jboss.org/maven2"
val guiceyfruit = "GuiceyFruit" at "http://guiceyfruit.googlecode.com/svn/repo/releases/"
- val google = "google" at "http://google-maven-repository.googlecode.com/svn/repository"
- val m2 = "m2" at "http://download.java.net/maven/2"
+ val google = "Google" at "http://google-maven-repository.googlecode.com/svn/repository"
+ val java_net = "java.net" at "http://download.java.net/maven/2"
val scala_tools_snapshots = "scala-tools snapshots" at "http://scala-tools.org/repo-snapshots"
+ val scala_tools_releases = "scala-tools releases" at "http://scala-tools.org/repo-releases"
// ------------------------------------------------------------
// project defintions
@@ -98,6 +99,7 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) {
lazy val akka_persistence = project("akka-persistence", "akka-persistence", new AkkaPersistenceParentProject(_))
lazy val akka_cluster = project("akka-cluster", "akka-cluster", new AkkaClusterParentProject(_))
lazy val akka_spring = project("akka-spring", "akka-spring", new AkkaSpringProject(_), akka_core)
+ lazy val akka_jta = project("akka-jta", "akka-jta", new AkkaJTAProject(_), akka_core)
lazy val akka_servlet = project("akka-servlet", "akka-servlet", new AkkaServletProject(_),
akka_core, akka_rest, akka_camel)
lazy val akka_kernel = project("akka-kernel", "akka-kernel", new AkkaKernelProject(_),
@@ -146,7 +148,8 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) {
" dist/akka-persistence-cassandra_%s-%s.jar".format(buildScalaVersion, version) +
" dist/akka-servlet_%s-%s.jar".format(buildScalaVersion, version) +
" dist/akka-kernel_%s-%s.jar".format(buildScalaVersion, version) +
- " dist/akka-spring_%s-%s.jar".format(buildScalaVersion, version)
+ " dist/akka-spring_%s-%s.jar".format(buildScalaVersion, version) +
+ " dist/akka-jta_%s-%s.jar".format(buildScalaVersion, version)
)
// ------------------------------------------------------------
@@ -324,6 +327,14 @@ class AkkaParent(info: ProjectInfo) extends DefaultProject(info) {
val junit = "junit" % "junit" % "4.5" % "test"
}
+ class AkkaJTAProject(info: ProjectInfo) extends AkkaDefaultProject(info, distPath) {
+ val atomikos_transactions = "com.atomikos" % "transactions" % "3.2.3" % "compile"
+ val atomikos_transactions_jta = "com.atomikos" % "transactions-jta" % "3.2.3" % "compile"
+ val atomikos_transactions_api = "com.atomikos" % "transactions-api" % "3.2.3" % "compile"
+ //val atomikos_transactions_util = "com.atomikos" % "transactions-util" % "3.2.3" % "compile"
+ val jta_spec = "org.apache.geronimo.specs" % "geronimo-jta_1.1_spec" % "1.1.1" % "compile"
+ }
+
// examples
class AkkaFunTestProject(info: ProjectInfo) extends DefaultProject(info) {
val jackson_core_asl = "org.codehaus.jackson" % "jackson-core-asl" % "1.2.1" % "compile"
From 7883b73052fd6ee94f2699163111518f9ff4c66d Mon Sep 17 00:00:00 2001
From: Viktor Klang
Date: Sat, 10 Apr 2010 23:59:40 +0200
Subject: [PATCH 02/41] 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 03/41] 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 04/41] 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 4728c3c1f07c26cd00f8366ffc0763cd28c9672b Mon Sep 17 00:00:00 2001
From: Michael Kober
Date: Wed, 14 Apr 2010 09:56:01 +0200
Subject: [PATCH 05/41] 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 06/41] 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 07/41] 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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