!tra #3920 Remove deprecated akka-transactor
This commit is contained in:
parent
9f906b2de9
commit
9cc586b674
19 changed files with 2 additions and 1300 deletions
|
|
@ -25,4 +25,5 @@ Removed Deprecated Features
|
||||||
The following, previously deprecated, features have been removed:
|
The following, previously deprecated, features have been removed:
|
||||||
|
|
||||||
* akka-dataflow
|
* akka-dataflow
|
||||||
|
* akka-transactor
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
#########################################
|
|
||||||
# Akka Transactor Reference Config File #
|
|
||||||
#########################################
|
|
||||||
|
|
||||||
# This is the reference config file that contains all the default settings.
|
|
||||||
# Make your edits/overrides in your application.conf.
|
|
||||||
|
|
||||||
akka {
|
|
||||||
# akka.transactor is deprecated in 2.3
|
|
||||||
transactor {
|
|
||||||
# The timeout used for coordinated transactions across actors
|
|
||||||
coordinated-timeout = 5s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor
|
|
||||||
|
|
||||||
import akka.AkkaException
|
|
||||||
import akka.util.Timeout
|
|
||||||
import scala.concurrent.stm.{ CommitBarrier, InTxn }
|
|
||||||
import java.util.concurrent.Callable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Akka-specific exception for coordinated transactions.
|
|
||||||
*/
|
|
||||||
@deprecated("akka.transactor will be removed", "2.3")
|
|
||||||
class CoordinatedTransactionException(message: String, cause: Throwable) extends AkkaException(message, cause) {
|
|
||||||
def this(msg: String) = this(msg, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coordinated transactions across actors.
|
|
||||||
*/
|
|
||||||
@deprecated("akka.transactor will be removed", "2.3")
|
|
||||||
object Coordinated {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Coordinated with the given message and Timeout
|
|
||||||
* @param message - the message which will be coordinated
|
|
||||||
* @param timeout - the timeout for the coordination
|
|
||||||
* @return a new Coordinated
|
|
||||||
*/
|
|
||||||
def apply(message: Any = null)(implicit timeout: Timeout): Coordinated =
|
|
||||||
new Coordinated(message, CommitBarrier(timeout.duration.toMillis).addMember())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param c - a Coordinated to be unapplied
|
|
||||||
* @return the message associated with the given Coordinated
|
|
||||||
*/
|
|
||||||
def unapply(c: Coordinated): Option[Any] = Some(c.message)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `Coordinated` is a message wrapper that adds a `CommitBarrier` for explicitly
|
|
||||||
* coordinating transactions across actors or threads.
|
|
||||||
*
|
|
||||||
* Creating a `Coordinated` will create a commit barrier with initially one member.
|
|
||||||
* For each member in the coordination set a transaction is expected to be created using
|
|
||||||
* the coordinated atomic method, or the coordination cancelled using the cancel method.
|
|
||||||
*
|
|
||||||
* The number of included members must match the number of transactions, otherwise a
|
|
||||||
* successful transaction cannot be coordinated.
|
|
||||||
* <br/><br/>
|
|
||||||
*
|
|
||||||
* To start a new coordinated transaction set that you will also participate in just create
|
|
||||||
* a `Coordinated` object:
|
|
||||||
*
|
|
||||||
* {{{
|
|
||||||
* val coordinated = Coordinated()
|
|
||||||
* }}}
|
|
||||||
* <br/>
|
|
||||||
*
|
|
||||||
* To start a coordinated transaction that you won't participate in yourself you can create a
|
|
||||||
* `Coordinated` object with a message and send it directly to an actor. The recipient of the message
|
|
||||||
* will be the first member of the coordination set:
|
|
||||||
*
|
|
||||||
* {{{
|
|
||||||
* actor ! Coordinated(Message)
|
|
||||||
* }}}
|
|
||||||
* <br/>
|
|
||||||
*
|
|
||||||
* To receive a coordinated message in an actor simply match it in a case statement:
|
|
||||||
*
|
|
||||||
* {{{
|
|
||||||
* def receive = {
|
|
||||||
* case coordinated @ Coordinated(Message) => ...
|
|
||||||
* }
|
|
||||||
* }}}
|
|
||||||
* <br/>
|
|
||||||
*
|
|
||||||
* To include another actor in the same coordinated transaction set that you've created or
|
|
||||||
* received, use the apply method on that object. This will increment the number of parties
|
|
||||||
* involved by one and create a new `Coordinated` object to be sent.
|
|
||||||
*
|
|
||||||
* {{{
|
|
||||||
* actor ! coordinated(Message)
|
|
||||||
* }}}
|
|
||||||
* <br/>
|
|
||||||
*
|
|
||||||
* To enter the coordinated transaction use the atomic method of the coordinated object:
|
|
||||||
*
|
|
||||||
* {{{
|
|
||||||
* coordinated.atomic { implicit txn =>
|
|
||||||
* // do something in transaction ...
|
|
||||||
* }
|
|
||||||
* }}}
|
|
||||||
*
|
|
||||||
* The coordinated transaction will wait for the other transactions before committing.
|
|
||||||
* If any of the coordinated transactions fail then they all fail.
|
|
||||||
*
|
|
||||||
* @see [[akka.transactor.Transactor]] for an actor that implements coordinated transactions
|
|
||||||
*/
|
|
||||||
@deprecated("akka.transactor will be removed", "2.3")
|
|
||||||
class Coordinated(val message: Any, member: CommitBarrier.Member) {
|
|
||||||
|
|
||||||
// Java API constructors
|
|
||||||
|
|
||||||
def this(message: Any, timeout: Timeout) = this(message, CommitBarrier(timeout.duration.toMillis).addMember())
|
|
||||||
|
|
||||||
def this(timeout: Timeout) = this(null, timeout)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new Coordinated object and increment the number of members by one.
|
|
||||||
* Use this method to ''pass on'' the coordination.
|
|
||||||
*/
|
|
||||||
def apply(msg: Any): Coordinated = new Coordinated(msg, member.commitBarrier.addMember())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new Coordinated object but *do not* increment the number of members by one.
|
|
||||||
* Only use this method if you know this is what you need.
|
|
||||||
*/
|
|
||||||
def noIncrement(msg: Any): Coordinated = new Coordinated(msg, member)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Java API: get the message for this Coordinated.
|
|
||||||
*/
|
|
||||||
def getMessage(): Any = message
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Java API: create a new Coordinated object and increment the number of members by one.
|
|
||||||
* Use this method to ''pass on'' the coordination.
|
|
||||||
*/
|
|
||||||
def coordinate(msg: Any): Coordinated = apply(msg)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delimits the coordinated transaction. The transaction will wait for all other transactions
|
|
||||||
* in this coordination before committing. The timeout is specified when creating the Coordinated.
|
|
||||||
*
|
|
||||||
* @throws CoordinatedTransactionException if the coordinated transaction fails.
|
|
||||||
*/
|
|
||||||
def atomic[A](body: InTxn ⇒ A): A = {
|
|
||||||
member.atomic(body) match {
|
|
||||||
case Right(result) ⇒ result
|
|
||||||
case Left(CommitBarrier.MemberUncaughtExceptionCause(x)) ⇒
|
|
||||||
throw new CoordinatedTransactionException("Exception in coordinated atomic", x)
|
|
||||||
case Left(cause) ⇒
|
|
||||||
throw new CoordinatedTransactionException("Failed due to " + cause)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Java API: coordinated atomic method that accepts a `java.lang.Runnable`.
|
|
||||||
* Delimits the coordinated transaction. The transaction will wait for all other transactions
|
|
||||||
* in this coordination before committing. The timeout is specified when creating the Coordinated.
|
|
||||||
*
|
|
||||||
* @throws CoordinatedTransactionException if the coordinated transaction fails.
|
|
||||||
*/
|
|
||||||
def atomic(runnable: Runnable): Unit = atomic { _ ⇒ runnable.run }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Java API: coordinated atomic method that accepts a `java.util.concurrent.Callable`.
|
|
||||||
* Delimits the coordinated transaction. The transaction will wait for all other transactions
|
|
||||||
* in this coordination before committing. The timeout is specified when creating the Coordinated.
|
|
||||||
*
|
|
||||||
* @throws CoordinatedTransactionException if the coordinated transaction fails.
|
|
||||||
*/
|
|
||||||
def atomic[A](callable: Callable[A]): A = atomic { _ ⇒ callable.call }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An empty coordinated atomic block. Can be used to complete the number of members involved
|
|
||||||
* and wait for all transactions to complete.
|
|
||||||
*/
|
|
||||||
def await(): Unit = atomic(txn ⇒ ())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancel this Coordinated transaction.
|
|
||||||
*/
|
|
||||||
def cancel(info: Any): Unit = member.cancel(CommitBarrier.UserCancel(info))
|
|
||||||
}
|
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor
|
|
||||||
|
|
||||||
import language.postfixOps
|
|
||||||
|
|
||||||
import akka.actor.{ Actor, ActorRef }
|
|
||||||
import scala.concurrent.stm.InTxn
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used for specifying actor refs and messages to send to during coordination.
|
|
||||||
*/
|
|
||||||
@deprecated("akka.transactor will be removed", "2.3")
|
|
||||||
final case class SendTo(actor: ActorRef, message: Option[Any] = None)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An actor with built-in support for coordinated transactions.
|
|
||||||
*
|
|
||||||
* Transactors implement the general pattern for using [[akka.transactor.Coordinated]] where
|
|
||||||
* coordination messages are sent to other transactors then the coordinated transaction is
|
|
||||||
* entered. Transactors can also accept explicitly sent `Coordinated` messages.
|
|
||||||
* <br/><br/>
|
|
||||||
*
|
|
||||||
* Simple transactors will just implement the `atomically` method which is similar to
|
|
||||||
* the actor `receive` method but runs within a coordinated transaction.
|
|
||||||
*
|
|
||||||
* Example of a simple transactor that will join a coordinated transaction:
|
|
||||||
*
|
|
||||||
* {{{
|
|
||||||
* class Counter extends Transactor {
|
|
||||||
* val count = Ref(0)
|
|
||||||
*
|
|
||||||
* def atomically = implicit txn => {
|
|
||||||
* case Increment => count transform (_ + 1)
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }}}
|
|
||||||
* <br/>
|
|
||||||
*
|
|
||||||
* To coordinate with other transactors override the `coordinate` method.
|
|
||||||
* The `coordinate` method maps a message to a set of [[akka.transactor.SendTo]]
|
|
||||||
* objects, pairs of `ActorRef` and a message.
|
|
||||||
* You can use the `include` and `sendTo` methods to easily coordinate with other transactors.
|
|
||||||
* The `include` method will send on the same message that was received to other transactors.
|
|
||||||
* The `sendTo` method allows you to specify both the actor to send to, and message to send.
|
|
||||||
*
|
|
||||||
* Example of coordinating an increment:
|
|
||||||
*
|
|
||||||
* {{{
|
|
||||||
* class FriendlyCounter(friend: ActorRef) extends Transactor {
|
|
||||||
* val count = Ref(0)
|
|
||||||
*
|
|
||||||
* override def coordinate = {
|
|
||||||
* case Increment => include(friend)
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* def atomically = implicit txn => {
|
|
||||||
* case Increment => count transform (_ + 1)
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }}}
|
|
||||||
* <br/>
|
|
||||||
*
|
|
||||||
* Using `include` to include more than one transactor:
|
|
||||||
*
|
|
||||||
* {{{
|
|
||||||
* override def coordinate = {
|
|
||||||
* case Message => include(actor1, actor2, actor3)
|
|
||||||
* }
|
|
||||||
* }}}
|
|
||||||
* <br/>
|
|
||||||
*
|
|
||||||
* Using `sendTo` to coordinate transactions but send on a different message
|
|
||||||
* than the one that was received:
|
|
||||||
*
|
|
||||||
* {{{
|
|
||||||
* override def coordinate = {
|
|
||||||
* case Message => sendTo(someActor -> SomeOtherMessage)
|
|
||||||
* case SomeMessage => sendTo(actor1 -> Message1, actor2 -> Message2)
|
|
||||||
* }
|
|
||||||
* }}}
|
|
||||||
* <br/>
|
|
||||||
*
|
|
||||||
* To execute directly before or after the coordinated transaction, override
|
|
||||||
* the `before` and `after` methods. These methods also expect partial functions
|
|
||||||
* like the receive method. They do not execute within the transaction.
|
|
||||||
*
|
|
||||||
* To completely bypass coordinated transactions override the `normally` method.
|
|
||||||
* Any message matched by `normally` will not be matched by the other methods,
|
|
||||||
* and will not be involved in coordinated transactions. In this method you
|
|
||||||
* can implement normal actor behavior, or use the normal STM atomic for
|
|
||||||
* local transactions.
|
|
||||||
*
|
|
||||||
* @see [[akka.transactor.Coordinated]]
|
|
||||||
*/
|
|
||||||
@deprecated("akka.transactor will be removed", "2.3")
|
|
||||||
trait Transactor extends Actor {
|
|
||||||
private val settings = TransactorExtension(context.system)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implement a general pattern for using coordinated transactions.
|
|
||||||
*/
|
|
||||||
final def receive = {
|
|
||||||
case coordinated @ Coordinated(message) ⇒ {
|
|
||||||
val others = (coordinate orElse alone)(message)
|
|
||||||
for (sendTo ← others) {
|
|
||||||
sendTo.actor ! coordinated(sendTo.message.getOrElse(message))
|
|
||||||
}
|
|
||||||
(before orElse doNothing)(message)
|
|
||||||
coordinated.atomic { txn ⇒ (atomically(txn) orElse doNothing)(message) }
|
|
||||||
(after orElse doNothing)(message)
|
|
||||||
}
|
|
||||||
case message ⇒ {
|
|
||||||
if (normally.isDefinedAt(message)) normally(message)
|
|
||||||
else receive(Coordinated(message)(settings.CoordinatedTimeout))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override this method to coordinate with other transactors.
|
|
||||||
* The other transactors are added to the coordinated transaction barrier
|
|
||||||
* and sent a Coordinated message. The message to send can be specified
|
|
||||||
* or otherwise the same message as received is sent. Use the 'include' and
|
|
||||||
* 'sendTo' methods to easily create the set of transactors to be involved.
|
|
||||||
*/
|
|
||||||
def coordinate: PartialFunction[Any, Set[SendTo]] = alone
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default coordination - no other transactors.
|
|
||||||
*/
|
|
||||||
def alone: PartialFunction[Any, Set[SendTo]] = { case _ ⇒ nobody }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Empty set of transactors to send to.
|
|
||||||
*/
|
|
||||||
def nobody: Set[SendTo] = Set.empty
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Include other actors in this coordinated transaction and send
|
|
||||||
* them the same message as received. Use as the result in 'coordinated'.
|
|
||||||
*/
|
|
||||||
def include(actors: ActorRef*): Set[SendTo] = actors map (SendTo(_)) toSet
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Include other actors in this coordinated transaction and specify the message
|
|
||||||
* to send by providing ActorRef -> Message pairs. Use as the result in 'coordinated'.
|
|
||||||
*/
|
|
||||||
def sendTo(pairs: (ActorRef, Any)*): Set[SendTo] = pairs map (p ⇒ SendTo(p._1, Some(p._2))) toSet
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Receive block that runs before the coordinated transaction is entered.
|
|
||||||
*/
|
|
||||||
def before: Receive = doNothing
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Receive block to run inside the coordinated transaction.
|
|
||||||
* This is a function from InTxn to Receive block.
|
|
||||||
*
|
|
||||||
* For example:
|
|
||||||
* {{{
|
|
||||||
* def atomically = implicit txn => {
|
|
||||||
* case Increment => count transform (_ + 1)
|
|
||||||
* }
|
|
||||||
* }}}
|
|
||||||
*/
|
|
||||||
def atomically: InTxn ⇒ Receive
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Receive block that runs after the coordinated transaction.
|
|
||||||
*/
|
|
||||||
def after: Receive = doNothing
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bypass transactionality and behave like a normal actor.
|
|
||||||
*/
|
|
||||||
def normally: Receive = doNothing
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default catch-all for the different Receive methods.
|
|
||||||
*/
|
|
||||||
def doNothing: Receive = EmptyReceive
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* INTERNAL API
|
|
||||||
*/
|
|
||||||
private[akka] object EmptyReceive extends PartialFunction[Any, Unit] {
|
|
||||||
def apply(any: Any): Unit = ()
|
|
||||||
def isDefinedAt(any: Any): Boolean = false
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
package akka.transactor
|
|
||||||
|
|
||||||
import akka.actor.{ ActorSystem, ExtensionId, ExtensionIdProvider, ExtendedActorSystem }
|
|
||||||
import akka.actor.Extension
|
|
||||||
import com.typesafe.config.Config
|
|
||||||
import akka.util.Timeout
|
|
||||||
import scala.concurrent.duration.Duration
|
|
||||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TransactorExtension is an Akka Extension to hold settings for transactors.
|
|
||||||
*/
|
|
||||||
@deprecated("akka.transactor will be removed", "2.3")
|
|
||||||
object TransactorExtension extends ExtensionId[TransactorSettings] with ExtensionIdProvider {
|
|
||||||
override def get(system: ActorSystem): TransactorSettings = super.get(system)
|
|
||||||
override def lookup: TransactorExtension.type = TransactorExtension
|
|
||||||
override def createExtension(system: ExtendedActorSystem): TransactorSettings = new TransactorSettings(system.settings.config)
|
|
||||||
}
|
|
||||||
|
|
||||||
@deprecated("akka.transactor will be removed", "2.3")
|
|
||||||
class TransactorSettings(val config: Config) extends Extension {
|
|
||||||
import config._
|
|
||||||
val CoordinatedTimeout: Timeout = Timeout(Duration(getMilliseconds("akka.transactor.coordinated-timeout"), MILLISECONDS))
|
|
||||||
}
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor
|
|
||||||
|
|
||||||
import akka.actor.{ UntypedActor, ActorRef }
|
|
||||||
import java.util.{ Set ⇒ JSet }
|
|
||||||
import java.util.Collections.{ emptySet, singleton ⇒ singletonSet }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An UntypedActor version of transactor for using from Java.
|
|
||||||
*/
|
|
||||||
@deprecated("akka.transactor will be removed", "2.3")
|
|
||||||
abstract class UntypedTransactor extends UntypedActor {
|
|
||||||
import scala.collection.JavaConverters.asScalaSetConverter
|
|
||||||
|
|
||||||
private val settings = TransactorExtension(context.system)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implement a general pattern for using coordinated transactions.
|
|
||||||
*/
|
|
||||||
@throws(classOf[Exception])
|
|
||||||
final def onReceive(message: Any) {
|
|
||||||
message match {
|
|
||||||
case coordinated @ Coordinated(message) ⇒ {
|
|
||||||
for (sendTo ← coordinate(message).asScala) {
|
|
||||||
sendTo.actor ! coordinated(sendTo.message.getOrElse(message))
|
|
||||||
}
|
|
||||||
before(message)
|
|
||||||
coordinated.atomic { txn ⇒ atomically(message) }
|
|
||||||
after(message)
|
|
||||||
}
|
|
||||||
case message ⇒ {
|
|
||||||
val normal = normally(message)
|
|
||||||
if (!normal) onReceive(Coordinated(message)(settings.CoordinatedTimeout))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Override this method to coordinate with other transactors.
|
|
||||||
* The other transactors are added to the coordinated transaction barrier
|
|
||||||
* and sent a Coordinated message. The message to send can be specified
|
|
||||||
* or otherwise the same message as received is sent. Use the 'include' and
|
|
||||||
* 'sendTo' methods to easily create the set of transactors to be involved.
|
|
||||||
*/
|
|
||||||
@throws(classOf[Exception])
|
|
||||||
def coordinate(message: Any): JSet[SendTo] = nobody
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Empty set of transactors to send to.
|
|
||||||
*/
|
|
||||||
def nobody: JSet[SendTo] = emptySet()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For including one other actor in this coordinated transaction and sending
|
|
||||||
* them the same message as received. Use as the result in `coordinated`.
|
|
||||||
*/
|
|
||||||
def include(actor: ActorRef): JSet[SendTo] = singletonSet(SendTo(actor))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For including one other actor in this coordinated transaction and specifying the
|
|
||||||
* message to send. Use as the result in `coordinated`.
|
|
||||||
*/
|
|
||||||
def include(actor: ActorRef, message: Any): JSet[SendTo] = singletonSet(SendTo(actor, Some(message)))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For including another actor in this coordinated transaction and sending
|
|
||||||
* them the same message as received. Use to create the result in `coordinated`.
|
|
||||||
*/
|
|
||||||
def sendTo(actor: ActorRef): SendTo = SendTo(actor)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For including another actor in this coordinated transaction and specifying the
|
|
||||||
* message to send. Use to create the result in `coordinated`.
|
|
||||||
*/
|
|
||||||
def sendTo(actor: ActorRef, message: Any): SendTo = SendTo(actor, Some(message))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Receive block that runs before the coordinated transaction is entered.
|
|
||||||
*/
|
|
||||||
@throws(classOf[Exception])
|
|
||||||
def before(message: Any) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Receive block to run inside the coordinated transaction.
|
|
||||||
*/
|
|
||||||
@throws(classOf[Exception])
|
|
||||||
def atomically(message: Any)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Receive block that runs after the coordinated transaction.
|
|
||||||
*/
|
|
||||||
@throws(classOf[Exception])
|
|
||||||
def after(message: Any) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bypass transactionality and behave like a normal actor.
|
|
||||||
*/
|
|
||||||
@throws(classOf[Exception])
|
|
||||||
def normally(message: Any): Boolean = false
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor;
|
|
||||||
|
|
||||||
public class ExpectedFailureException extends RuntimeException {
|
|
||||||
public ExpectedFailureException() {
|
|
||||||
super("Expected failure");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor;
|
|
||||||
|
|
||||||
import akka.actor.ActorRef;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
|
|
||||||
public class Increment {
|
|
||||||
private List<ActorRef> friends;
|
|
||||||
private CountDownLatch latch;
|
|
||||||
|
|
||||||
public Increment(List<ActorRef> friends, CountDownLatch latch) {
|
|
||||||
this.friends = friends;
|
|
||||||
this.latch = latch;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ActorRef> getFriends() {
|
|
||||||
return friends;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CountDownLatch getLatch() {
|
|
||||||
return latch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
|
|
||||||
import scala.concurrent.stm.Ref;
|
|
||||||
import scala.concurrent.stm.japi.STM;
|
|
||||||
import akka.actor.ActorRef;
|
|
||||||
import akka.actor.UntypedActor;
|
|
||||||
|
|
||||||
public class UntypedCoordinatedCounter extends UntypedActor {
|
|
||||||
private String name;
|
|
||||||
private Ref.View<Integer> count = STM.newRef(0);
|
|
||||||
|
|
||||||
public UntypedCoordinatedCounter(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onReceive(Object incoming) throws Exception {
|
|
||||||
if (incoming instanceof Coordinated) {
|
|
||||||
Coordinated coordinated = (Coordinated) incoming;
|
|
||||||
Object message = coordinated.getMessage();
|
|
||||||
if (message instanceof Increment) {
|
|
||||||
Increment increment = (Increment) message;
|
|
||||||
List<ActorRef> friends = increment.getFriends();
|
|
||||||
final CountDownLatch latch = increment.getLatch();
|
|
||||||
final Runnable countDown = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (!friends.isEmpty()) {
|
|
||||||
Increment coordMessage = new Increment(friends.subList(1, friends.size()), latch);
|
|
||||||
friends.get(0).tell(coordinated.coordinate(coordMessage), getSelf());
|
|
||||||
}
|
|
||||||
coordinated.atomic(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
STM.increment(count, 1);
|
|
||||||
STM.afterCompletion(countDown);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if ("GetCount".equals(incoming)) {
|
|
||||||
getSender().tell(count.get(), getSelf());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
import akka.testkit.*;
|
|
||||||
import org.junit.*;
|
|
||||||
|
|
||||||
import akka.actor.ActorSystem;
|
|
||||||
import akka.actor.ActorRef;
|
|
||||||
import akka.actor.Props;
|
|
||||||
import scala.concurrent.Await;
|
|
||||||
import scala.concurrent.Future;
|
|
||||||
import static akka.pattern.Patterns.ask;
|
|
||||||
|
|
||||||
import akka.util.Timeout;
|
|
||||||
|
|
||||||
import static akka.japi.Util.immutableSeq;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import scala.collection.JavaConverters;
|
|
||||||
import scala.collection.immutable.Seq;
|
|
||||||
|
|
||||||
public class UntypedCoordinatedIncrementTest {
|
|
||||||
|
|
||||||
@ClassRule
|
|
||||||
public static AkkaJUnitActorSystemResource actorSystemResource =
|
|
||||||
new AkkaJUnitActorSystemResource("UntypedCoordinatedIncrementTest", AkkaSpec.testConf());
|
|
||||||
|
|
||||||
private final ActorSystem system = actorSystemResource.getSystem();
|
|
||||||
|
|
||||||
List<ActorRef> counters;
|
|
||||||
ActorRef failer;
|
|
||||||
|
|
||||||
int numCounters = 3;
|
|
||||||
int timeoutSeconds = 5;
|
|
||||||
|
|
||||||
Timeout timeout = new Timeout(timeoutSeconds, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void initialize() {
|
|
||||||
counters = new ArrayList<ActorRef>();
|
|
||||||
for (int i = 1; i <= numCounters; i++) {
|
|
||||||
final String name = "counter" + i;
|
|
||||||
ActorRef counter = system.actorOf(Props.create(UntypedCoordinatedCounter.class, name));
|
|
||||||
counters.add(counter);
|
|
||||||
}
|
|
||||||
failer = system.actorOf(Props.create(UntypedFailer.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void incrementAllCountersWithSuccessfulTransaction() throws Exception {
|
|
||||||
CountDownLatch incrementLatch = new CountDownLatch(numCounters);
|
|
||||||
Increment message = new Increment(counters.subList(1, counters.size()), incrementLatch);
|
|
||||||
counters.get(0).tell(new Coordinated(message, timeout), null);
|
|
||||||
try {
|
|
||||||
incrementLatch.await(timeoutSeconds, TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException exception) {
|
|
||||||
}
|
|
||||||
for (ActorRef counter : counters) {
|
|
||||||
Future<Object> future = ask(counter, "GetCount", timeout);
|
|
||||||
int count = (Integer) Await.result(future, timeout.duration());
|
|
||||||
assertEquals(1, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void incrementNoCountersWithFailingTransaction() throws Exception {
|
|
||||||
EventFilter expectedFailureFilter = (EventFilter) new ErrorFilter(ExpectedFailureException.class);
|
|
||||||
EventFilter coordinatedFilter = (EventFilter) new ErrorFilter(CoordinatedTransactionException.class);
|
|
||||||
Seq<EventFilter> ignoreExceptions = seq(expectedFailureFilter, coordinatedFilter);
|
|
||||||
system.eventStream().publish(new TestEvent.Mute(ignoreExceptions));
|
|
||||||
CountDownLatch incrementLatch = new CountDownLatch(numCounters);
|
|
||||||
List<ActorRef> actors = new ArrayList<ActorRef>(counters);
|
|
||||||
actors.add(failer);
|
|
||||||
Increment message = new Increment(actors.subList(1, actors.size()), incrementLatch);
|
|
||||||
actors.get(0).tell(new Coordinated(message, timeout), null);
|
|
||||||
try {
|
|
||||||
incrementLatch.await(timeoutSeconds, TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException exception) {
|
|
||||||
}
|
|
||||||
for (ActorRef counter : counters) {
|
|
||||||
Future<Object>future = ask(counter, "GetCount", timeout);
|
|
||||||
int count = (Integer) Await.result(future, timeout.duration());
|
|
||||||
assertEquals(0, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public <A> Seq<A> seq(A... args) {
|
|
||||||
return immutableSeq(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import scala.concurrent.stm.Ref;
|
|
||||||
import scala.concurrent.stm.japi.STM;
|
|
||||||
import akka.actor.ActorRef;
|
|
||||||
|
|
||||||
public class UntypedCounter extends UntypedTransactor {
|
|
||||||
private String name;
|
|
||||||
private Ref.View<Integer> count = STM.newRef(0);
|
|
||||||
|
|
||||||
public UntypedCounter(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public Set<SendTo> coordinate(Object message) {
|
|
||||||
if (message instanceof Increment) {
|
|
||||||
Increment increment = (Increment) message;
|
|
||||||
List<ActorRef> friends = increment.getFriends();
|
|
||||||
if (!friends.isEmpty()) {
|
|
||||||
Increment coordMessage = new Increment(friends.subList(1, friends.size()), increment.getLatch());
|
|
||||||
return include(friends.get(0), coordMessage);
|
|
||||||
} else {
|
|
||||||
return nobody();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nobody();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void atomically(Object message) {
|
|
||||||
if (message instanceof Increment) {
|
|
||||||
STM.increment(count, 1);
|
|
||||||
final Increment increment = (Increment) message;
|
|
||||||
Runnable countDown = new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
increment.getLatch().countDown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
STM.afterCompletion(countDown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public boolean normally(Object message) {
|
|
||||||
if ("GetCount".equals(message)) {
|
|
||||||
getSender().tell(count.get(), getSelf());
|
|
||||||
return true;
|
|
||||||
} else return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor;
|
|
||||||
|
|
||||||
public class UntypedFailer extends UntypedTransactor {
|
|
||||||
public void atomically(Object message) throws Exception {
|
|
||||||
throw new ExpectedFailureException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
import akka.testkit.*;
|
|
||||||
import org.junit.*;
|
|
||||||
|
|
||||||
import akka.actor.ActorSystem;
|
|
||||||
import akka.actor.ActorRef;
|
|
||||||
import akka.actor.Props;
|
|
||||||
import scala.concurrent.Await;
|
|
||||||
import scala.concurrent.Future;
|
|
||||||
import static akka.pattern.Patterns.ask;
|
|
||||||
|
|
||||||
import akka.util.Timeout;
|
|
||||||
|
|
||||||
import static akka.japi.Util.immutableSeq;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import scala.collection.JavaConverters;
|
|
||||||
import scala.collection.immutable.Seq;
|
|
||||||
|
|
||||||
public class UntypedTransactorTest {
|
|
||||||
|
|
||||||
@ClassRule
|
|
||||||
public static AkkaJUnitActorSystemResource actorSystemResource =
|
|
||||||
new AkkaJUnitActorSystemResource("UntypedTransactorTest", AkkaSpec.testConf());
|
|
||||||
|
|
||||||
private final ActorSystem system = actorSystemResource.getSystem();
|
|
||||||
|
|
||||||
List<ActorRef> counters;
|
|
||||||
ActorRef failer;
|
|
||||||
|
|
||||||
int numCounters = 3;
|
|
||||||
int timeoutSeconds = 5;
|
|
||||||
|
|
||||||
Timeout timeout = new Timeout(timeoutSeconds, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void initialize() {
|
|
||||||
counters = new ArrayList<ActorRef>();
|
|
||||||
for (int i = 1; i <= numCounters; i++) {
|
|
||||||
final String name = "counter" + i;
|
|
||||||
ActorRef counter = system.actorOf(Props.create(UntypedCounter.class, name));
|
|
||||||
counters.add(counter);
|
|
||||||
}
|
|
||||||
failer = system.actorOf(Props.create(UntypedFailer.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void incrementAllCountersWithSuccessfulTransaction() throws Exception {
|
|
||||||
CountDownLatch incrementLatch = new CountDownLatch(numCounters);
|
|
||||||
Increment message = new Increment(counters.subList(1, counters.size()),
|
|
||||||
incrementLatch);
|
|
||||||
counters.get(0).tell(message, null);
|
|
||||||
try {
|
|
||||||
incrementLatch.await(timeoutSeconds, TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException exception) {
|
|
||||||
}
|
|
||||||
for (ActorRef counter : counters) {
|
|
||||||
Future<Object> future = ask(counter, "GetCount", timeout);
|
|
||||||
int count = (Integer) Await.result(future, timeout.duration());
|
|
||||||
assertEquals(1, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void incrementNoCountersWithFailingTransaction() throws Exception {
|
|
||||||
EventFilter expectedFailureFilter = (EventFilter) new ErrorFilter(
|
|
||||||
ExpectedFailureException.class);
|
|
||||||
EventFilter coordinatedFilter = (EventFilter) new ErrorFilter(
|
|
||||||
CoordinatedTransactionException.class);
|
|
||||||
Seq<EventFilter> ignoreExceptions = seq(expectedFailureFilter,
|
|
||||||
coordinatedFilter);
|
|
||||||
system.eventStream().publish(new TestEvent.Mute(ignoreExceptions));
|
|
||||||
CountDownLatch incrementLatch = new CountDownLatch(numCounters);
|
|
||||||
List<ActorRef> actors = new ArrayList<ActorRef>(counters);
|
|
||||||
actors.add(failer);
|
|
||||||
Increment message = new Increment(actors.subList(1, actors.size()),
|
|
||||||
incrementLatch);
|
|
||||||
actors.get(0).tell(message, null);
|
|
||||||
try {
|
|
||||||
incrementLatch.await(timeoutSeconds, TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException exception) {
|
|
||||||
}
|
|
||||||
for (ActorRef counter : counters) {
|
|
||||||
Future<Object> future = ask(counter, "GetCount", timeout);
|
|
||||||
int count = (Integer) Await.result(future, timeout.duration());
|
|
||||||
assertEquals(0, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public <A> Seq<A> seq(A... args) {
|
|
||||||
return immutableSeq(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor
|
|
||||||
|
|
||||||
import org.scalatest.BeforeAndAfterAll
|
|
||||||
|
|
||||||
import scala.concurrent.Await
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
import scala.concurrent.stm._
|
|
||||||
import scala.collection.immutable
|
|
||||||
import akka.actor._
|
|
||||||
import akka.util.Timeout
|
|
||||||
import akka.testkit._
|
|
||||||
import akka.pattern.{ AskTimeoutException, ask }
|
|
||||||
|
|
||||||
object CoordinatedIncrement {
|
|
||||||
|
|
||||||
val config = """
|
|
||||||
akka {
|
|
||||||
actor {
|
|
||||||
default-dispatcher {
|
|
||||||
executor = "thread-pool-executor"
|
|
||||||
thread-pool-executor {
|
|
||||||
core-pool-size-min = 5
|
|
||||||
core-pool-size-max = 16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
final case class Increment(friends: immutable.Seq[ActorRef])
|
|
||||||
case object GetCount
|
|
||||||
|
|
||||||
class Counter(name: String) extends Actor {
|
|
||||||
val count = Ref(0)
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case coordinated @ Coordinated(Increment(friends)) ⇒ {
|
|
||||||
if (friends.nonEmpty) {
|
|
||||||
friends.head ! coordinated(Increment(friends.tail))
|
|
||||||
}
|
|
||||||
coordinated.atomic { implicit t ⇒
|
|
||||||
count transform (_ + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case GetCount ⇒ sender ! count.single.get
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExpectedFailureException extends RuntimeException("Expected failure")
|
|
||||||
|
|
||||||
class Failer extends Actor {
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case coordinated @ Coordinated(Increment(friends)) ⇒ {
|
|
||||||
coordinated.atomic { t ⇒
|
|
||||||
throw new ExpectedFailureException
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
|
||||||
class CoordinatedIncrementSpec extends AkkaSpec(CoordinatedIncrement.config) with BeforeAndAfterAll {
|
|
||||||
import CoordinatedIncrement._
|
|
||||||
|
|
||||||
val numCounters = 4
|
|
||||||
|
|
||||||
def actorOfs = {
|
|
||||||
def createCounter(i: Int) = system.actorOf(Props(classOf[Counter], "counter" + i))
|
|
||||||
val counters = (1 to numCounters) map createCounter
|
|
||||||
val failer = system.actorOf(Props[Failer])
|
|
||||||
(counters, failer)
|
|
||||||
}
|
|
||||||
|
|
||||||
"Coordinated increment" should {
|
|
||||||
implicit val timeout = Timeout(2.seconds.dilated)
|
|
||||||
"increment all counters by one with successful transactions" in {
|
|
||||||
val (counters, failer) = actorOfs
|
|
||||||
val coordinated = Coordinated()
|
|
||||||
counters(0) ! coordinated(Increment(counters.tail))
|
|
||||||
coordinated.await
|
|
||||||
for (counter ← counters) {
|
|
||||||
Await.result((counter ? GetCount).mapTo[Int], timeout.duration) should be(1)
|
|
||||||
}
|
|
||||||
counters foreach (system.stop(_))
|
|
||||||
system.stop(failer)
|
|
||||||
}
|
|
||||||
|
|
||||||
"increment no counters with a failing transaction" in {
|
|
||||||
val ignoreExceptions = Seq(
|
|
||||||
EventFilter[ExpectedFailureException](),
|
|
||||||
EventFilter[CoordinatedTransactionException](),
|
|
||||||
EventFilter[AskTimeoutException]())
|
|
||||||
filterEvents(ignoreExceptions) {
|
|
||||||
val (counters, failer) = actorOfs
|
|
||||||
val coordinated = Coordinated()
|
|
||||||
counters(0) ! Coordinated(Increment(counters.tail :+ failer))
|
|
||||||
coordinated.await
|
|
||||||
for (counter ← counters) {
|
|
||||||
Await.result(counter ? GetCount, timeout.duration) should be(0)
|
|
||||||
}
|
|
||||||
counters foreach (system.stop(_))
|
|
||||||
system.stop(failer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor
|
|
||||||
|
|
||||||
import language.postfixOps
|
|
||||||
|
|
||||||
import org.scalatest.BeforeAndAfterAll
|
|
||||||
|
|
||||||
import scala.concurrent.Await
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
import scala.concurrent.stm._
|
|
||||||
import scala.collection.immutable
|
|
||||||
import scala.util.Random.{ nextInt ⇒ random }
|
|
||||||
import scala.util.control.NonFatal
|
|
||||||
import akka.actor._
|
|
||||||
import akka.testkit._
|
|
||||||
import akka.testkit.TestEvent.Mute
|
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
import akka.pattern.{ AskTimeoutException, ask }
|
|
||||||
import akka.util.Timeout
|
|
||||||
|
|
||||||
object FickleFriends {
|
|
||||||
final case class FriendlyIncrement(friends: immutable.Seq[ActorRef], timeout: Timeout, latch: CountDownLatch)
|
|
||||||
final case class Increment(friends: immutable.Seq[ActorRef])
|
|
||||||
case object GetCount
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Coordinator will keep trying to coordinate an increment until successful.
|
|
||||||
*/
|
|
||||||
class Coordinator(name: String) extends Actor {
|
|
||||||
val count = Ref(0)
|
|
||||||
|
|
||||||
def increment(implicit txn: InTxn) = {
|
|
||||||
count transform (_ + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case FriendlyIncrement(friends, timeout, latch) ⇒ {
|
|
||||||
var success = false
|
|
||||||
while (!success) {
|
|
||||||
try {
|
|
||||||
val coordinated = Coordinated()(timeout)
|
|
||||||
if (friends.nonEmpty) {
|
|
||||||
friends.head ! coordinated(Increment(friends.tail))
|
|
||||||
}
|
|
||||||
coordinated.atomic { implicit t ⇒
|
|
||||||
increment
|
|
||||||
Txn.afterCommit { status ⇒
|
|
||||||
success = true
|
|
||||||
latch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
case NonFatal(_) ⇒ () // swallow exceptions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case GetCount ⇒ sender ! count.single.get
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExpectedFailureException(message: String) extends RuntimeException(message)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FickleCounter randomly fails at different points with 50% chance of failing overall.
|
|
||||||
*/
|
|
||||||
class FickleCounter(name: String) extends Actor {
|
|
||||||
val count = Ref(0)
|
|
||||||
|
|
||||||
val maxFailures = 3
|
|
||||||
var failures = 0
|
|
||||||
|
|
||||||
def increment(implicit txn: InTxn) = {
|
|
||||||
count transform (_ + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
def failIf(x: Int, y: Int) = {
|
|
||||||
if (x == y && failures < maxFailures) {
|
|
||||||
failures += 1
|
|
||||||
throw new ExpectedFailureException("Random fail at position " + x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
case coordinated @ Coordinated(Increment(friends)) ⇒ {
|
|
||||||
val failAt = random(8)
|
|
||||||
failIf(failAt, 0)
|
|
||||||
if (friends.nonEmpty) {
|
|
||||||
friends.head ! coordinated(Increment(friends.tail))
|
|
||||||
}
|
|
||||||
failIf(failAt, 1)
|
|
||||||
coordinated.atomic { implicit t ⇒
|
|
||||||
failIf(failAt, 2)
|
|
||||||
increment
|
|
||||||
failIf(failAt, 3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case GetCount ⇒ sender ! count.single.get
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
|
||||||
class FickleFriendsSpec extends AkkaSpec with BeforeAndAfterAll {
|
|
||||||
import FickleFriends._
|
|
||||||
|
|
||||||
implicit val timeout = Timeout(5.seconds.dilated)
|
|
||||||
|
|
||||||
val numCounters = 2
|
|
||||||
|
|
||||||
def actorOfs = {
|
|
||||||
def createCounter(i: Int) = system.actorOf(Props(classOf[FickleCounter], "counter" + i))
|
|
||||||
val counters = (1 to numCounters) map createCounter
|
|
||||||
val coordinator = system.actorOf(Props(classOf[Coordinator], "coordinator"))
|
|
||||||
(counters, coordinator)
|
|
||||||
}
|
|
||||||
|
|
||||||
"Coordinated fickle friends" should {
|
|
||||||
"eventually succeed to increment all counters by one" in {
|
|
||||||
val ignoreExceptions = immutable.Seq(
|
|
||||||
EventFilter[ExpectedFailureException](),
|
|
||||||
EventFilter[CoordinatedTransactionException](),
|
|
||||||
EventFilter[AskTimeoutException]())
|
|
||||||
system.eventStream.publish(Mute(ignoreExceptions))
|
|
||||||
val (counters, coordinator) = actorOfs
|
|
||||||
val latch = new CountDownLatch(1)
|
|
||||||
coordinator ! FriendlyIncrement(counters, timeout, latch)
|
|
||||||
latch.await // this could take a while
|
|
||||||
Await.result(coordinator ? GetCount, timeout.duration) should be(1)
|
|
||||||
for (counter ← counters) {
|
|
||||||
Await.result(counter ? GetCount, timeout.duration) should be(1)
|
|
||||||
}
|
|
||||||
counters foreach (system.stop(_))
|
|
||||||
system.stop(coordinator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor
|
|
||||||
|
|
||||||
import org.scalatest.junit.JUnitWrapperSuite
|
|
||||||
|
|
||||||
class JavaUntypedCoordinatedSpec extends JUnitWrapperSuite(
|
|
||||||
"akka.transactor.UntypedCoordinatedIncrementTest",
|
|
||||||
Thread.currentThread.getContextClassLoader)
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor
|
|
||||||
|
|
||||||
import org.scalatest.junit.JUnitWrapperSuite
|
|
||||||
|
|
||||||
class JavaUntypedTransactorSpec extends JUnitWrapperSuite(
|
|
||||||
"akka.transactor.UntypedTransactorTest",
|
|
||||||
Thread.currentThread.getContextClassLoader)
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
package akka.transactor
|
|
||||||
|
|
||||||
import language.postfixOps
|
|
||||||
|
|
||||||
import akka.actor._
|
|
||||||
import scala.collection.immutable
|
|
||||||
import scala.concurrent.Await
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
import scala.concurrent.stm._
|
|
||||||
import akka.util.Timeout
|
|
||||||
import akka.testkit._
|
|
||||||
import akka.pattern.{ AskTimeoutException, ask }
|
|
||||||
|
|
||||||
object TransactorIncrement {
|
|
||||||
final case class Increment(friends: immutable.Seq[ActorRef], latch: TestLatch)
|
|
||||||
case object GetCount
|
|
||||||
|
|
||||||
class Counter(name: String) extends Transactor {
|
|
||||||
val count = Ref(0)
|
|
||||||
|
|
||||||
def increment(implicit txn: InTxn) = {
|
|
||||||
count transform (_ + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
override def coordinate = {
|
|
||||||
case Increment(friends, latch) ⇒ {
|
|
||||||
if (friends.nonEmpty) sendTo(friends.head -> Increment(friends.tail, latch))
|
|
||||||
else nobody
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def before = {
|
|
||||||
case i: Increment ⇒
|
|
||||||
}
|
|
||||||
|
|
||||||
def atomically = implicit txn ⇒ {
|
|
||||||
case Increment(friends, latch) ⇒ {
|
|
||||||
increment
|
|
||||||
Txn.afterCompletion { status ⇒ latch.countDown() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def after = {
|
|
||||||
case i: Increment ⇒
|
|
||||||
}
|
|
||||||
|
|
||||||
override def normally = {
|
|
||||||
case GetCount ⇒ sender ! count.single.get
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExpectedFailureException extends RuntimeException("Expected failure")
|
|
||||||
|
|
||||||
class Failer extends Transactor {
|
|
||||||
def atomically = implicit txn ⇒ {
|
|
||||||
case _ ⇒ throw new ExpectedFailureException
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object SimpleTransactor {
|
|
||||||
final case class Set(ref: Ref[Int], value: Int, latch: TestLatch)
|
|
||||||
|
|
||||||
class Setter extends Transactor {
|
|
||||||
def atomically = implicit txn ⇒ {
|
|
||||||
case Set(ref, value, latch) ⇒ {
|
|
||||||
ref() = value
|
|
||||||
Txn.afterCompletion { status ⇒ latch.countDown() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
|
||||||
class TransactorSpec extends AkkaSpec {
|
|
||||||
import TransactorIncrement._
|
|
||||||
import SimpleTransactor._
|
|
||||||
|
|
||||||
implicit val timeout = Timeout(5.seconds.dilated)
|
|
||||||
|
|
||||||
val numCounters = 3
|
|
||||||
|
|
||||||
def createTransactors = {
|
|
||||||
def createCounter(i: Int) = system.actorOf(Props(classOf[Counter], "counter" + i))
|
|
||||||
val counters = (1 to numCounters) map createCounter
|
|
||||||
val failer = system.actorOf(Props[Failer])
|
|
||||||
(counters, failer)
|
|
||||||
}
|
|
||||||
|
|
||||||
"Transactor increment" should {
|
|
||||||
"increment all counters by one with successful transactions" in {
|
|
||||||
val (counters, failer) = createTransactors
|
|
||||||
val incrementLatch = TestLatch(numCounters)
|
|
||||||
counters(0) ! Increment(counters.tail, incrementLatch)
|
|
||||||
Await.ready(incrementLatch, 5 seconds)
|
|
||||||
for (counter ← counters) {
|
|
||||||
Await.result(counter ? GetCount, timeout.duration) should be(1)
|
|
||||||
}
|
|
||||||
counters foreach (system.stop(_))
|
|
||||||
system.stop(failer)
|
|
||||||
}
|
|
||||||
|
|
||||||
"increment no counters with a failing transaction" in {
|
|
||||||
val ignoreExceptions = Seq(
|
|
||||||
EventFilter[ExpectedFailureException](),
|
|
||||||
EventFilter[CoordinatedTransactionException](),
|
|
||||||
EventFilter[AskTimeoutException]())
|
|
||||||
filterEvents(ignoreExceptions) {
|
|
||||||
val (counters, failer) = createTransactors
|
|
||||||
val failLatch = TestLatch(numCounters)
|
|
||||||
counters(0) ! Increment(counters.tail :+ failer, failLatch)
|
|
||||||
Await.ready(failLatch, 5 seconds)
|
|
||||||
for (counter ← counters) {
|
|
||||||
Await.result(counter ? GetCount, timeout.duration) should be(0)
|
|
||||||
}
|
|
||||||
counters foreach (system.stop(_))
|
|
||||||
system.stop(failer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"Transactor" should {
|
|
||||||
"be usable without overriding normally" in {
|
|
||||||
val transactor = system.actorOf(Props[Setter])
|
|
||||||
val ref = Ref(0)
|
|
||||||
val latch = TestLatch(1)
|
|
||||||
transactor ! Set(ref, 5, latch)
|
|
||||||
Await.ready(latch, 5 seconds)
|
|
||||||
val value = ref.single.get
|
|
||||||
value should be(5)
|
|
||||||
system.stop(transactor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -75,7 +75,7 @@ object AkkaBuild extends Build {
|
||||||
// add reportBinaryIssues to validatePullRequest on minor version maintenance branch
|
// add reportBinaryIssues to validatePullRequest on minor version maintenance branch
|
||||||
validatePullRequest <<= (Unidoc.unidoc, SphinxSupport.generate in Sphinx in docs) map { (_, _) => }
|
validatePullRequest <<= (Unidoc.unidoc, SphinxSupport.generate in Sphinx in docs) map { (_, _) => }
|
||||||
),
|
),
|
||||||
aggregate = Seq(actor, testkit, actorTests, remote, remoteTests, camel, cluster, slf4j, agent, transactor,
|
aggregate = Seq(actor, testkit, actorTests, remote, remoteTests, camel, cluster, slf4j, agent,
|
||||||
persistence, mailboxes, zeroMQ, kernel, osgi, docs, contrib, samples, multiNodeTestkit)
|
persistence, mailboxes, zeroMQ, kernel, osgi, docs, contrib, samples, multiNodeTestkit)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -252,16 +252,6 @@ object AkkaBuild extends Build {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val transactor = Project(
|
|
||||||
id = "akka-transactor",
|
|
||||||
base = file("akka-transactor"),
|
|
||||||
dependencies = Seq(actor, testkit % "test->test"),
|
|
||||||
settings = defaultSettings ++ formatSettings ++ scaladocSettings ++ javadocSettings ++ OSGi.transactor ++ Seq(
|
|
||||||
libraryDependencies ++= Dependencies.transactor,
|
|
||||||
previousArtifact := akkaPreviousArtifact("akka-transactor")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
lazy val persistence = Project(
|
lazy val persistence = Project(
|
||||||
id = "akka-persistence-experimental",
|
id = "akka-persistence-experimental",
|
||||||
base = file("akka-persistence"),
|
base = file("akka-persistence"),
|
||||||
|
|
@ -1029,8 +1019,6 @@ object AkkaBuild extends Build {
|
||||||
|
|
||||||
val slf4j = exports(Seq("akka.event.slf4j.*"))
|
val slf4j = exports(Seq("akka.event.slf4j.*"))
|
||||||
|
|
||||||
val transactor = exports(Seq("akka.transactor.*"))
|
|
||||||
|
|
||||||
val persistence = exports(Seq("akka.persistence.*"), imports = Seq(protobufImport()))
|
val persistence = exports(Seq("akka.persistence.*"), imports = Seq(protobufImport()))
|
||||||
|
|
||||||
val testkit = exports(Seq("akka.testkit.*"))
|
val testkit = exports(Seq("akka.testkit.*"))
|
||||||
|
|
@ -1151,8 +1139,6 @@ object Dependencies {
|
||||||
|
|
||||||
val agent = Seq(scalaStm, Test.scalatest, Test.junit)
|
val agent = Seq(scalaStm, Test.scalatest, Test.junit)
|
||||||
|
|
||||||
val transactor = Seq(scalaStm, Test.scalatest, Test.junit)
|
|
||||||
|
|
||||||
val persistence = Seq(levelDB, levelDBNative, protobuf, Test.scalatest, Test.junit, Test.commonsIo)
|
val persistence = Seq(levelDB, levelDBNative, protobuf, Test.scalatest, Test.junit, Test.commonsIo)
|
||||||
|
|
||||||
val mailboxes = Seq(Test.scalatest, Test.junit)
|
val mailboxes = Seq(Test.scalatest, Test.junit)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue