Merge pull request #179 from jboner/migrate-transactor
Migrate transactor to scala-stm
This commit is contained in:
commit
0eb1ecbbf4
71 changed files with 1345 additions and 3829 deletions
|
|
@ -1,513 +0,0 @@
|
|||
.. _stm-java:
|
||||
|
||||
Software Transactional Memory (Java)
|
||||
====================================
|
||||
|
||||
.. sidebar:: Contents
|
||||
|
||||
.. contents:: :local:
|
||||
|
||||
Overview of STM
|
||||
---------------
|
||||
|
||||
An `STM <http://en.wikipedia.org/wiki/Software_transactional_memory>`_ turns the Java heap into a transactional data set with begin/commit/rollback semantics. Very much like a regular database. It implements the first three letters in ACID; ACI:
|
||||
* (failure) Atomicity: all changes during the execution of a transaction make it, or none make it. This only counts for transactional datastructures.
|
||||
* Consistency: a transaction gets a consistent of reality (in Akka you get the Oracle version of the SERIALIZED isolation level).
|
||||
* Isolated: changes made by concurrent execution transactions are not visible to each other.
|
||||
|
||||
Generally, the STM is not needed that often when working with Akka. Some use-cases (that we can think of) are:
|
||||
|
||||
- When you really need composable message flows across many actors updating their **internal local** state but need them to do that atomically in one big transaction. Might not often, but when you do need this then you are screwed without it.
|
||||
- When you want to share a datastructure across actors.
|
||||
- When you need to use the persistence modules.
|
||||
|
||||
Akka’s STM implements the concept in `Clojure’s <http://clojure.org/>`_ STM view on state in general. Please take the time to read `this excellent document <http://clojure.org/state>`_ and view `this presentation <http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey>`_ by Rich Hickey (the genius behind Clojure), since it forms the basis of Akka’s view on STM and state in general.
|
||||
|
||||
The STM is based on Transactional References (referred to as Refs). Refs are memory cells, holding an (arbitrary) immutable value, that implement CAS (Compare-And-Swap) semantics and are managed and enforced by the STM for coordinated changes across many Refs. They are implemented using the excellent `Multiverse STM <http://multiverse.codehaus.org/overview.html>`_.
|
||||
|
||||
Working with immutable collections can sometimes give bad performance due to extensive copying. Scala provides so-called persistent datastructures which makes working with immutable collections fast. They are immutable but with constant time access and modification. The use of structural sharing and an insert or update does not ruin the old structure, hence “persistent”. Makes working with immutable composite types fast. The persistent datastructures currently consist of a Map and Vector.
|
||||
|
||||
Simple example
|
||||
--------------
|
||||
|
||||
Here is a simple example of an incremental counter using STM. This shows creating a ``Ref``, a transactional reference, and then modifying it within a transaction, which is delimited by an ``Atomic`` anonymous inner class.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
final Ref<Integer> ref = new Ref<Integer>(0);
|
||||
|
||||
public int counter() {
|
||||
return new Atomic<Integer>() {
|
||||
public Integer atomically() {
|
||||
int inc = ref.get() + 1;
|
||||
ref.set(inc);
|
||||
return inc;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
counter();
|
||||
// -> 1
|
||||
|
||||
counter();
|
||||
// -> 2
|
||||
|
||||
|
||||
Ref
|
||||
---
|
||||
|
||||
Refs (transactional references) are mutable references to values and through the STM allow the safe sharing of mutable data. To ensure safety the value stored in a Ref should be immutable. The value referenced by a Ref can only be accessed or swapped within a transaction. Refs separate identity from value.
|
||||
|
||||
Creating a Ref
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
You can create a Ref with or without an initial value.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
// giving an initial value
|
||||
final Ref<Integer> ref = new Ref<Integer>(0);
|
||||
|
||||
// specifying a type but no initial value
|
||||
final Ref<Integer> ref = new Ref<Integer>();
|
||||
|
||||
Accessing the value of a Ref
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Use ``get`` to access the value of a Ref. Note that if no initial value has been given then the value is initially ``null``.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
final Ref<Integer> ref = new Ref<Integer>(0);
|
||||
|
||||
Integer value = new Atomic<Integer>() {
|
||||
public Integer atomically() {
|
||||
return ref.get();
|
||||
}
|
||||
}.execute();
|
||||
// -> value = 0
|
||||
|
||||
Changing the value of a Ref
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To set a new value for a Ref you can use ``set`` (or equivalently ``swap``), which sets the new value and returns the old value.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
final Ref<Integer> ref = new Ref<Integer>(0);
|
||||
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
return ref.set(5);
|
||||
}
|
||||
}.execute();
|
||||
|
||||
|
||||
Transactions
|
||||
------------
|
||||
|
||||
A transaction is delimited using an ``Atomic`` anonymous inner class.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
// ...
|
||||
}
|
||||
}.execute();
|
||||
|
||||
All changes made to transactional objects are isolated from other changes, all make it or non make it (so failure atomicity) and are consistent. With the AkkaSTM you automatically have the Oracle version of the SERIALIZED isolation level, lower isolation is not possible. To make it fully serialized, set the writeskew property that checks if a writeskew problem is allowed to happen.
|
||||
|
||||
Retries
|
||||
^^^^^^^
|
||||
|
||||
A transaction is automatically retried when it runs into some read or write conflict, until the operation completes, an exception (throwable) is thrown or when there are too many retries. When a read or writeconflict is encountered, the transaction uses a bounded exponential backoff to prevent cause more contention and give other transactions some room to complete.
|
||||
|
||||
If you are using non transactional resources in an atomic block, there could be problems because a transaction can be retried. If you are using print statements or logging, it could be that they are called more than once. So you need to be prepared to deal with this. One of the possible solutions is to work with a deferred or compensating task that is executed after the transaction aborts or commits.
|
||||
|
||||
Unexpected retries
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It can happen for the first few executions that you get a few failures of execution that lead to unexpected retries, even though there is not any read or writeconflict. The cause of this is that speculative transaction configuration/selection is used. There are transactions optimized for a single transactional object, for 1..n and for n to unlimited. So based on the execution of the transaction, the system learns; it begins with a cheap one and upgrades to more expensive ones. Once it has learned, it will reuse this knowledge. It can be activated/deactivated using the speculative property on the TransactionFactoryBuilder. In most cases it is best use the default value (enabled) so you get more out of performance.
|
||||
|
||||
Coordinated transactions and Transactors
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you need coordinated transactions across actors or threads then see :ref:`transactors-java`.
|
||||
|
||||
Configuring transactions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It's possible to configure transactions. The ``Atomic`` class can take a ``TransactionFactory``, which can determine properties of the transaction. A default transaction factory is used if none is specified. You can create a ``TransactionFactory`` with a ``TransactionFactoryBuilder``.
|
||||
|
||||
Configuring transactions with a ``TransactionFactory``:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
TransactionFactory txFactory = new TransactionFactoryBuilder()
|
||||
.setReadonly(true)
|
||||
.build();
|
||||
|
||||
new Atomic<Object>(txFactory) {
|
||||
public Object atomically() {
|
||||
// read only transaction
|
||||
return ...;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
The following settings are possible on a TransactionFactory:
|
||||
|
||||
- familyName - Family name for transactions. Useful for debugging because the familyName is shown in exceptions, logging and in the future also will be used for profiling.
|
||||
- readonly - Sets transaction as readonly. Readonly transactions are cheaper and can be used to prevent modification to transactional objects.
|
||||
- maxRetries - The maximum number of times a transaction will retry.
|
||||
- timeout - The maximum time a transaction will block for.
|
||||
- trackReads - Whether all reads should be tracked. Needed for blocking operations. Readtracking makes a transaction more expensive, but makes subsequent reads cheaper and also lowers the chance of a readconflict.
|
||||
- writeSkew - Whether writeskew is allowed. Disable with care.
|
||||
- blockingAllowed - Whether explicit retries are allowed.
|
||||
- interruptible - Whether a blocking transaction can be interrupted if it is blocked.
|
||||
- speculative - Whether speculative configuration should be enabled.
|
||||
- quickRelease - Whether locks should be released as quickly as possible (before whole commit).
|
||||
- propagation - For controlling how nested transactions behave.
|
||||
- traceLevel - Transaction trace level.
|
||||
|
||||
You can also specify the default values for some of these options in :ref:`configuration`.
|
||||
|
||||
Transaction lifecycle listeners
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It's possible to have code that will only run on the successful commit of a transaction, or when a transaction aborts. You can do this by adding ``deferred`` or ``compensating`` blocks to a transaction.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
import static akka.stm.StmUtils.deferred;
|
||||
import static akka.stm.StmUtils.compensating;
|
||||
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
deferred(new Runnable() {
|
||||
public void run() {
|
||||
// executes when transaction commits
|
||||
}
|
||||
});
|
||||
compensating(new Runnable() {
|
||||
public void run() {
|
||||
// executes when transaction aborts
|
||||
}
|
||||
});
|
||||
// ...
|
||||
return something;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
Blocking transactions
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can block in a transaction until a condition is met by using an explicit ``retry``. To use ``retry`` you also need to configure the transaction to allow explicit retries.
|
||||
|
||||
Here is an example of using ``retry`` to block until an account has enough money for a withdrawal. This is also an example of using actors and STM together.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
public class Transfer {
|
||||
private final Ref<Double> from;
|
||||
private final Ref<Double> to;
|
||||
private final double amount;
|
||||
|
||||
public Transfer(Ref<Double> from, Ref<Double> to, double amount) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public Ref<Double> getFrom() { return from; }
|
||||
public Ref<Double> getTo() { return to; }
|
||||
public double getAmount() { return amount; }
|
||||
}
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
import static akka.stm.StmUtils.retry;
|
||||
import akka.actor.*;
|
||||
import akka.util.FiniteDuration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import akka.event.EventHandler;
|
||||
|
||||
public class Transferer extends UntypedActor {
|
||||
TransactionFactory txFactory = new TransactionFactoryBuilder()
|
||||
.setBlockingAllowed(true)
|
||||
.setTrackReads(true)
|
||||
.setTimeout(new FiniteDuration(60, TimeUnit.SECONDS))
|
||||
.build();
|
||||
|
||||
public void onReceive(Object message) throws Exception {
|
||||
if (message instanceof Transfer) {
|
||||
Transfer transfer = (Transfer) message;
|
||||
final Ref<Double> from = transfer.getFrom();
|
||||
final Ref<Double> to = transfer.getTo();
|
||||
final double amount = transfer.getAmount();
|
||||
new Atomic(txFactory) {
|
||||
public Object atomically() {
|
||||
if (from.get() < amount) {
|
||||
EventHandler.info(this, "not enough money - retrying");
|
||||
retry();
|
||||
}
|
||||
EventHandler.info(this, "transferring");
|
||||
from.set(from.get() - amount);
|
||||
to.set(to.get() + amount);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
import akka.actor.*;
|
||||
|
||||
public class Main {
|
||||
public static void main(String...args) throws Exception {
|
||||
final Ref<Double> account1 = new Ref<Double>(100.0);
|
||||
final Ref<Double> account2 = new Ref<Double>(100.0);
|
||||
|
||||
ActorRef transferer = Actors.actorOf(Transferer.class);
|
||||
|
||||
transferer.tell(new Transfer(account1, account2, 500.0));
|
||||
// Transferer: not enough money - retrying
|
||||
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
return account1.set(account1.get() + 2000);
|
||||
}
|
||||
}.execute();
|
||||
// Transferer: transferring
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
Double acc1 = new Atomic<Double>() {
|
||||
public Double atomically() {
|
||||
return account1.get();
|
||||
}
|
||||
}.execute();
|
||||
|
||||
Double acc2 = new Atomic<Double>() {
|
||||
public Double atomically() {
|
||||
return account2.get();
|
||||
}
|
||||
}.execute();
|
||||
|
||||
|
||||
|
||||
System.out.println("Account 1: " + acc1);
|
||||
// Account 1: 1600.0
|
||||
|
||||
System.out.println("Account 2: " + acc2);
|
||||
// Account 2: 600.0
|
||||
|
||||
transferer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
Alternative blocking transactions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can also have two alternative blocking transactions, one of which can succeed first, with ``EitherOrElse``.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
public class Branch {
|
||||
private final Ref<Integer> left;
|
||||
private final Ref<Integer> right;
|
||||
private final double amount;
|
||||
|
||||
public Branch(Ref<Integer> left, Ref<Integer> right, int amount) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public Ref<Integer> getLeft() { return left; }
|
||||
|
||||
public Ref<Integer> getRight() { return right; }
|
||||
|
||||
public double getAmount() { return amount; }
|
||||
}
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.stm.*;
|
||||
import static akka.stm.StmUtils.retry;
|
||||
import akka.util.FiniteDuration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import akka.event.EventHandler;
|
||||
|
||||
public class Brancher extends UntypedActor {
|
||||
TransactionFactory txFactory = new TransactionFactoryBuilder()
|
||||
.setBlockingAllowed(true)
|
||||
.setTrackReads(true)
|
||||
.setTimeout(new FiniteDuration(60, TimeUnit.SECONDS))
|
||||
.build();
|
||||
|
||||
public void onReceive(Object message) throws Exception {
|
||||
if (message instanceof Branch) {
|
||||
Branch branch = (Branch) message;
|
||||
final Ref<Integer> left = branch.getLeft();
|
||||
final Ref<Integer> right = branch.getRight();
|
||||
final double amount = branch.getAmount();
|
||||
new Atomic<Integer>(txFactory) {
|
||||
public Integer atomically() {
|
||||
return new EitherOrElse<Integer>() {
|
||||
public Integer either() {
|
||||
if (left.get() < amount) {
|
||||
EventHandler.info(this, "not enough on left - retrying");
|
||||
retry();
|
||||
}
|
||||
EventHandler.info(this, "going left");
|
||||
return left.get();
|
||||
}
|
||||
public Integer orElse() {
|
||||
if (right.get() < amount) {
|
||||
EventHandler.info(this, "not enough on right - retrying");
|
||||
retry();
|
||||
}
|
||||
EventHandler.info(this, "going right");
|
||||
return right.get();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
import akka.actor.*;
|
||||
|
||||
public class Main2 {
|
||||
public static void main(String...args) throws Exception {
|
||||
final Ref<Integer> left = new Ref<Integer>(100);
|
||||
final Ref<Integer> right = new Ref<Integer>(100);
|
||||
|
||||
ActorRef brancher = Actors.actorOf(Brancher.class);
|
||||
|
||||
brancher.tell(new Branch(left, right, 500));
|
||||
// not enough on left - retrying
|
||||
// not enough on right - retrying
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
return right.set(right.get() + 1000);
|
||||
}
|
||||
}.execute();
|
||||
// going right
|
||||
|
||||
|
||||
|
||||
brancher.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Transactional datastructures
|
||||
----------------------------
|
||||
|
||||
Akka provides two datastructures that are managed by the STM.
|
||||
|
||||
- TransactionalMap
|
||||
- TransactionalVector
|
||||
|
||||
TransactionalMap and TransactionalVector look like regular mutable datastructures, they even implement the standard Scala 'Map' and 'RandomAccessSeq' interfaces, but they are implemented using persistent datastructures and managed references under the hood. Therefore they are safe to use in a concurrent environment. Underlying TransactionalMap is HashMap, an immutable Map but with near constant time access and modification operations. Similarly TransactionalVector uses a persistent Vector. See the Persistent Datastructures section below for more details.
|
||||
|
||||
Like managed references, TransactionalMap and TransactionalVector can only be modified inside the scope of an STM transaction.
|
||||
|
||||
Here is an example of creating and accessing a TransactionalMap:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
// assuming a User class
|
||||
|
||||
final TransactionalMap<String, User> users = new TransactionalMap<String, User>();
|
||||
|
||||
// fill users map (in a transaction)
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
users.put("bill", new User("bill"));
|
||||
users.put("mary", new User("mary"));
|
||||
users.put("john", new User("john"));
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
// access users map (in a transaction)
|
||||
User user = new Atomic<User>() {
|
||||
public User atomically() {
|
||||
return users.get("bill").get();
|
||||
}
|
||||
}.execute();
|
||||
|
||||
Here is an example of creating and accessing a TransactionalVector:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
// assuming an Address class
|
||||
|
||||
final TransactionalVector<Address> addresses = new TransactionalVector<Address>();
|
||||
|
||||
// fill addresses vector (in a transaction)
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
addresses.add(new Address("somewhere"));
|
||||
addresses.add(new Address("somewhere else"));
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
// access addresses vector (in a transaction)
|
||||
Address address = new Atomic<Address>() {
|
||||
public Address atomically() {
|
||||
return addresses.get(0);
|
||||
}
|
||||
}.execute();
|
||||
|
||||
|
||||
Persistent datastructures
|
||||
-------------------------
|
||||
|
||||
Akka's STM should only be used with immutable data. This can be costly if you have large datastructures and are using a naive copy-on-write. In order to make working with immutable datastructures fast enough Scala provides what are called Persistent Datastructures. There are currently two different ones:
|
||||
|
||||
- HashMap (`scaladoc <http://www.scala-lang.org/api/current/scala/collection/immutable/HashMap.html>`__)
|
||||
- Vector (`scaladoc <http://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html>`__)
|
||||
|
||||
They are immutable and each update creates a completely new version but they are using clever structural sharing in order to make them almost as fast, for both read and update, as regular mutable datastructures.
|
||||
|
||||
This illustration is taken from Rich Hickey's presentation. Copyright Rich Hickey 2009.
|
||||
|
||||
.. image:: ../images/clojure-trees.png
|
||||
|
||||
|
||||
|
|
@ -1,269 +0,0 @@
|
|||
.. _transactors-java:
|
||||
|
||||
Transactors (Java)
|
||||
==================
|
||||
|
||||
.. sidebar:: Contents
|
||||
|
||||
.. contents:: :local:
|
||||
|
||||
Why Transactors?
|
||||
----------------
|
||||
|
||||
Actors are excellent for solving problems where you have many independent processes that can work in isolation and only interact with other Actors through message passing. This model fits many problems. But the actor model is unfortunately a terrible model for implementing truly shared state. E.g. when you need to have consensus and a stable view of state across many components. The classic example is the bank account where clients can deposit and withdraw, in which each operation needs to be atomic. For detailed discussion on the topic see `this JavaOne presentation <http://www.slideshare.net/jboner/state-youre-doing-it-wrong-javaone-2009>`_.
|
||||
|
||||
**STM** on the other hand is excellent for problems where you need consensus and a stable view of the state by providing compositional transactional shared state. Some of the really nice traits of STM are that transactions compose, and it raises the abstraction level from lock-based concurrency.
|
||||
|
||||
Akka's Transactors combine Actors and STM to provide the best of the Actor model (concurrency and asynchronous event-based programming) and STM (compositional transactional shared state) by providing transactional, compositional, asynchronous, event-based message flows.
|
||||
|
||||
If you need Durability then you should not use one of the in-memory data structures but one of the persistent ones.
|
||||
|
||||
Generally, the STM is not needed very often when working with Akka. Some use-cases (that we can think of) are:
|
||||
|
||||
- When you really need composable message flows across many actors updating their **internal local** state but need them to do that atomically in one big transaction. Might not often, but when you do need this then you are screwed without it.
|
||||
- When you want to share a datastructure across actors.
|
||||
- When you need to use the persistence modules.
|
||||
|
||||
Actors and STM
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
You can combine Actors and STM in several ways. An Actor may use STM internally so that particular changes are guaranteed to be atomic. Actors may also share transactional datastructures as the STM provides safe shared state across threads.
|
||||
|
||||
It's also possible to coordinate transactions across Actors or threads so that either the transactions in a set all commit successfully or they all fail. This is the focus of Transactors and the explicit support for coordinated transactions in this section.
|
||||
|
||||
|
||||
Coordinated transactions
|
||||
------------------------
|
||||
|
||||
Akka provides an explicit mechanism for coordinating transactions across actors. Under the hood it uses a ``CountDownCommitBarrier``, similar to a CountDownLatch.
|
||||
|
||||
Here is an example of coordinating two simple counter UntypedActors so that they both increment together in coordinated transactions. If one of them was to fail to increment, the other would also fail.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
|
||||
public class Increment {
|
||||
private final ActorRef friend;
|
||||
|
||||
public Increment() {
|
||||
this.friend = null;
|
||||
}
|
||||
|
||||
public Increment(ActorRef friend) {
|
||||
this.friend = friend;
|
||||
}
|
||||
|
||||
public boolean hasFriend() {
|
||||
return friend != null;
|
||||
}
|
||||
|
||||
public ActorRef getFriend() {
|
||||
return friend;
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.actor.UntypedActor;
|
||||
import akka.stm.Ref;
|
||||
import akka.transactor.Atomically;
|
||||
import akka.transactor.Coordinated;
|
||||
|
||||
public class Counter extends UntypedActor {
|
||||
private Ref<Integer> count = new Ref(0);
|
||||
|
||||
private void increment() {
|
||||
count.set(count.get() + 1);
|
||||
}
|
||||
|
||||
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;
|
||||
if (increment.hasFriend()) {
|
||||
increment.getFriend().tell(coordinated.coordinate(new Increment()));
|
||||
}
|
||||
coordinated.atomic(new Atomically() {
|
||||
public void atomically() {
|
||||
increment();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (incoming.equals("GetCount")) {
|
||||
getContext().reply(count.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
ActorRef counter1 = actorOf(Counter.class);
|
||||
ActorRef counter2 = actorOf(Counter.class);
|
||||
|
||||
counter1.tell(new Coordinated(new Increment(counter2)));
|
||||
|
||||
To start a new coordinated transaction that you will also participate in, just create a ``Coordinated`` object:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
Coordinated coordinated = new Coordinated();
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
actor.tell(new Coordinated(new Message()));
|
||||
|
||||
To include another actor in the same coordinated transaction that you've created or received, use the ``coordinate`` method on that object. This will increment the number of parties involved by one and create a new ``Coordinated`` object to be sent.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
actor.tell(coordinated.coordinate(new Message()));
|
||||
|
||||
To enter the coordinated transaction use the atomic method of the coordinated object. This accepts either an ``akka.transactor.Atomically`` object, or an ``Atomic`` object the same as used normally in the STM (just don't execute it - the coordination will do that).
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
coordinated.atomic(new Atomically() {
|
||||
public void atomically() {
|
||||
// do something in a transaction
|
||||
}
|
||||
});
|
||||
|
||||
The coordinated transaction will wait for the other transactions before committing. If any of the coordinated transactions fail then they all fail.
|
||||
|
||||
|
||||
UntypedTransactor
|
||||
-----------------
|
||||
|
||||
UntypedTransactors are untyped actors that provide a general pattern for coordinating transactions, using the explicit coordination described above.
|
||||
|
||||
Here's an example of a simple untyped transactor that will join a coordinated transaction:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.transactor.UntypedTransactor;
|
||||
import akka.stm.Ref;
|
||||
|
||||
public class Counter extends UntypedTransactor {
|
||||
Ref<Integer> count = new Ref<Integer>(0);
|
||||
|
||||
@Override
|
||||
public void atomically(Object message) {
|
||||
if (message instanceof Increment) {
|
||||
count.set(count.get() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
You could send this Counter transactor a ``Coordinated(Increment)`` message. If you were to send it just an ``Increment`` message it will create its own ``Coordinated`` (but in this particular case wouldn't be coordinating transactions with any other transactors).
|
||||
|
||||
To coordinate with other transactors override the ``coordinate`` method. The ``coordinate`` method maps a message to a set of ``SendTo`` objects, pairs of ``ActorRef`` and a message. You can use the ``include`` and ``sendTo`` methods to easily coordinate with other transactors.
|
||||
|
||||
Example of coordinating an increment, similar to the explicitly coordinated example:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.transactor.UntypedTransactor;
|
||||
import akka.transactor.SendTo;
|
||||
import akka.stm.Ref;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class Counter extends UntypedTransactor {
|
||||
Ref<Integer> count = new Ref<Integer>(0);
|
||||
|
||||
@Override
|
||||
public Set<SendTo> coordinate(Object message) {
|
||||
if (message instanceof Increment) {
|
||||
Increment increment = (Increment) message;
|
||||
if (increment.hasFriend())
|
||||
return include(increment.getFriend(), new Increment());
|
||||
}
|
||||
return nobody();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void atomically(Object message) {
|
||||
if (message instanceof Increment) {
|
||||
count.set(count.get() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
To execute directly before or after the coordinated transaction, override the ``before`` and ``after`` methods. 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.
|
||||
|
||||
|
||||
Coordinating Typed Actors
|
||||
-------------------------
|
||||
|
||||
It's also possible to use coordinated transactions with typed actors. You can explicitly pass around ``Coordinated`` objects, or use built-in support with the ``@Coordinated`` annotation and the ``Coordination.coordinate`` method.
|
||||
|
||||
To specify a method should use coordinated transactions add the ``@Coordinated`` annotation. **Note**: the ``@Coordinated`` annotation will only work with void (one-way) methods.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
public interface Counter {
|
||||
@Coordinated public void increment();
|
||||
public Integer get();
|
||||
}
|
||||
|
||||
To coordinate transactions use a ``coordinate`` block. This accepts either an ``akka.transactor.Atomically`` object, or an ``Atomic`` object liked used in the STM (but don't execute it). The first boolean parameter specifies whether or not to wait for the transactions to complete.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
Coordination.coordinate(true, new Atomically() {
|
||||
public void atomically() {
|
||||
counter1.increment();
|
||||
counter2.increment();
|
||||
}
|
||||
});
|
||||
|
||||
Here's an example of using ``@Coordinated`` with a TypedActor to coordinate increments:
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.transactor.annotation.Coordinated;
|
||||
|
||||
public interface Counter {
|
||||
@Coordinated public void increment();
|
||||
public Integer get();
|
||||
}
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
import akka.actor.TypedActor;
|
||||
import akka.stm.Ref;
|
||||
|
||||
public class CounterImpl extends TypedActor implements Counter {
|
||||
private Ref<Integer> count = new Ref<Integer>(0);
|
||||
|
||||
public void increment() {
|
||||
count.set(count.get() + 1);
|
||||
}
|
||||
|
||||
public Integer get() {
|
||||
return count.get();
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
Counter counter1 = (Counter) TypedActor.newInstance(Counter.class, CounterImpl.class);
|
||||
Counter counter2 = (Counter) TypedActor.newInstance(Counter.class, CounterImpl.class);
|
||||
|
||||
Coordination.coordinate(true, new Atomically() {
|
||||
public void atomically() {
|
||||
counter1.increment();
|
||||
counter2.increment();
|
||||
}
|
||||
});
|
||||
|
||||
TypedActor.stop(counter1);
|
||||
TypedActor.stop(counter2);
|
||||
|
||||
|
|
@ -1,537 +0,0 @@
|
|||
|
||||
.. _stm-scala:
|
||||
|
||||
#######################################
|
||||
Software Transactional Memory (Scala)
|
||||
#######################################
|
||||
|
||||
.. sidebar:: Contents
|
||||
|
||||
.. contents:: :local:
|
||||
|
||||
Overview of STM
|
||||
===============
|
||||
|
||||
An `STM <http://en.wikipedia.org/wiki/Software_transactional_memory>`_ turns the
|
||||
Java heap into a transactional data set with begin/commit/rollback
|
||||
semantics. Very much like a regular database. It implements the first three
|
||||
letters in ACID; ACI:
|
||||
|
||||
* Atomic
|
||||
* Consistent
|
||||
* Isolated
|
||||
|
||||
Generally, the STM is not needed very often when working with Akka. Some
|
||||
use-cases (that we can think of) are:
|
||||
|
||||
- When you really need composable message flows across many actors updating
|
||||
their **internal local** state but need them to do that atomically in one big
|
||||
transaction. Might not be often, but when you do need this then you are
|
||||
screwed without it.
|
||||
- When you want to share a datastructure across actors.
|
||||
- When you need to use the persistence modules.
|
||||
|
||||
Akka’s STM implements the concept in `Clojure's <clojure>`_ STM view on state in
|
||||
general. Please take the time to read `this excellent document <clojure-state>`_
|
||||
and view `this presentation <clojure-presentation>`_ by Rich Hickey (the genius
|
||||
behind Clojure), since it forms the basis of Akka’s view on STM and state in
|
||||
general.
|
||||
|
||||
.. _clojure: http://clojure.org/
|
||||
.. _clojure-state: http://clojure.org/state
|
||||
.. _clojure-presentation: http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey
|
||||
|
||||
The STM is based on Transactional References (referred to as Refs). Refs are
|
||||
memory cells, holding an (arbitrary) immutable value, that implement CAS
|
||||
(Compare-And-Swap) semantics and are managed and enforced by the STM for
|
||||
coordinated changes across many Refs. They are implemented using the excellent
|
||||
`Multiverse STM <multiverse>`_.
|
||||
|
||||
.. _multiverse: http://multiverse.codehaus.org/overview.html
|
||||
|
||||
Working with immutable collections can sometimes give bad performance due to
|
||||
extensive copying. Scala provides so-called persistent datastructures which
|
||||
makes working with immutable collections fast. They are immutable but with
|
||||
constant time access and modification. They use structural sharing and an insert
|
||||
or update does not ruin the old structure, hence “persistent”. Makes working
|
||||
with immutable composite types fast. The persistent datastructures currently
|
||||
consist of a Map and Vector.
|
||||
|
||||
|
||||
Simple example
|
||||
==============
|
||||
|
||||
Here is a simple example of an incremental counter using STM. This shows
|
||||
creating a ``Ref``, a transactional reference, and then modifying it within a
|
||||
transaction, which is delimited by ``atomic``.
|
||||
|
||||
.. includecode:: code/StmDocSpec.scala#simple
|
||||
|
||||
|
||||
Ref
|
||||
---
|
||||
|
||||
Refs (transactional references) are mutable references to values and through the STM allow the safe sharing of mutable data. Refs separate identity from value. To ensure safety the value stored in a Ref should be immutable (they can of course contain refs themselves). The value referenced by a Ref can only be accessed or swapped within a transaction. If a transaction is not available, the call will be executed in its own transaction (the call will be atomic). This is a different approach than the Clojure Refs, where a missing transaction results in an error.
|
||||
|
||||
Creating a Ref
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
You can create a Ref with or without an initial value.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
|
||||
// giving an initial value
|
||||
val ref = Ref(0)
|
||||
|
||||
// specifying a type but no initial value
|
||||
val ref = Ref[Int]
|
||||
|
||||
Accessing the value of a Ref
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Use ``get`` to access the value of a Ref. Note that if no initial value has been given then the value is initially ``null``.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
|
||||
val ref = Ref(0)
|
||||
|
||||
atomic {
|
||||
ref.get
|
||||
}
|
||||
// -> 0
|
||||
|
||||
If there is a chance that the value of a Ref is null then you can use ``opt``, which will create an Option, either Some(value) or None, or you can provide a default value with ``getOrElse``. You can also check for null using ``isNull``.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
|
||||
val ref = Ref[Int]
|
||||
|
||||
atomic {
|
||||
ref.opt // -> None
|
||||
ref.getOrElse(0) // -> 0
|
||||
ref.isNull // -> true
|
||||
}
|
||||
|
||||
Changing the value of a Ref
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To set a new value for a Ref you can use ``set`` (or equivalently ``swap``), which sets the new value and returns the old value.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
|
||||
val ref = Ref(0)
|
||||
|
||||
atomic {
|
||||
ref.set(5)
|
||||
}
|
||||
// -> 0
|
||||
|
||||
atomic {
|
||||
ref.get
|
||||
}
|
||||
// -> 5
|
||||
|
||||
You can also use ``alter`` which accepts a function that takes the old value and creates a new value of the same type.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
|
||||
val ref = Ref(0)
|
||||
|
||||
atomic {
|
||||
ref alter (_ + 5)
|
||||
}
|
||||
// -> 5
|
||||
|
||||
val inc = (i: Int) => i + 1
|
||||
|
||||
atomic {
|
||||
ref alter inc
|
||||
}
|
||||
// -> 6
|
||||
|
||||
Refs in for-comprehensions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Ref is monadic and can be used in for-comprehensions.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
|
||||
val ref = Ref(1)
|
||||
|
||||
atomic {
|
||||
for (value <- ref) {
|
||||
// do something with value
|
||||
}
|
||||
}
|
||||
|
||||
val anotherRef = Ref(3)
|
||||
|
||||
atomic {
|
||||
for {
|
||||
value1 <- ref
|
||||
value2 <- anotherRef
|
||||
} yield (value1 + value2)
|
||||
}
|
||||
// -> Ref(4)
|
||||
|
||||
val emptyRef = Ref[Int]
|
||||
|
||||
atomic {
|
||||
for {
|
||||
value1 <- ref
|
||||
value2 <- emptyRef
|
||||
} yield (value1 + value2)
|
||||
}
|
||||
// -> Ref[Int]
|
||||
|
||||
|
||||
Transactions
|
||||
------------
|
||||
|
||||
A transaction is delimited using ``atomic``.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
atomic {
|
||||
// ...
|
||||
}
|
||||
|
||||
All changes made to transactional objects are isolated from other changes, all make it or non make it (so failure atomicity) and are consistent. With the AkkaSTM you automatically have the Oracle version of the SERIALIZED isolation level, lower isolation is not possible. To make it fully serialized, set the writeskew property that checks if a writeskew problem is allowed to happen.
|
||||
|
||||
Retries
|
||||
^^^^^^^
|
||||
|
||||
A transaction is automatically retried when it runs into some read or write conflict, until the operation completes, an exception (throwable) is thrown or when there are too many retries. When a read or writeconflict is encountered, the transaction uses a bounded exponential backoff to prevent cause more contention and give other transactions some room to complete.
|
||||
|
||||
If you are using non transactional resources in an atomic block, there could be problems because a transaction can be retried. If you are using print statements or logging, it could be that they are called more than once. So you need to be prepared to deal with this. One of the possible solutions is to work with a deferred or compensating task that is executed after the transaction aborts or commits.
|
||||
|
||||
Unexpected retries
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It can happen for the first few executions that you get a few failures of execution that lead to unexpected retries, even though there is not any read or writeconflict. The cause of this is that speculative transaction configuration/selection is used. There are transactions optimized for a single transactional object, for 1..n and for n to unlimited. So based on the execution of the transaction, the system learns; it begins with a cheap one and upgrades to more expensive ones. Once it has learned, it will reuse this knowledge. It can be activated/deactivated using the speculative property on the TransactionFactory. In most cases it is best use the default value (enabled) so you get more out of performance.
|
||||
|
||||
Coordinated transactions and Transactors
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you need coordinated transactions across actors or threads then see :ref:`transactors-scala`.
|
||||
|
||||
Configuring transactions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It's possible to configure transactions. The ``atomic`` method can take an implicit or explicit ``TransactionFactory``, which can determine properties of the transaction. A default transaction factory is used if none is specified explicitly or there is no implicit ``TransactionFactory`` in scope.
|
||||
|
||||
Configuring transactions with an **implicit** ``TransactionFactory``:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
|
||||
implicit val txFactory = TransactionFactory(readonly = true)
|
||||
|
||||
atomic {
|
||||
// read only transaction
|
||||
}
|
||||
|
||||
Configuring transactions with an **explicit** ``TransactionFactory``:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
|
||||
val txFactory = TransactionFactory(readonly = true)
|
||||
|
||||
atomic(txFactory) {
|
||||
// read only transaction
|
||||
}
|
||||
|
||||
The following settings are possible on a TransactionFactory:
|
||||
|
||||
- ``familyName`` - Family name for transactions. Useful for debugging.
|
||||
- ``readonly`` - Sets transaction as readonly. Readonly transactions are cheaper.
|
||||
- ``maxRetries`` - The maximum number of times a transaction will retry.
|
||||
- ``timeout`` - The maximum time a transaction will block for.
|
||||
- ``trackReads`` - Whether all reads should be tracked. Needed for blocking operations.
|
||||
- ``writeSkew`` - Whether writeskew is allowed. Disable with care.
|
||||
- ``blockingAllowed`` - Whether explicit retries are allowed.
|
||||
- ``interruptible`` - Whether a blocking transaction can be interrupted.
|
||||
- ``speculative`` - Whether speculative configuration should be enabled.
|
||||
- ``quickRelease`` - Whether locks should be released as quickly as possible (before whole commit).
|
||||
- ``propagation`` - For controlling how nested transactions behave.
|
||||
- ``traceLevel`` - Transaction trace level.
|
||||
|
||||
You can also specify the default values for some of these options in the :ref:`configuration`.
|
||||
|
||||
You can also determine at which level a transaction factory is shared or not shared, which affects the way in which the STM can optimise transactions.
|
||||
|
||||
Here is a shared transaction factory for all instances of an actor.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.actor._
|
||||
import akka.stm._
|
||||
|
||||
object MyActor {
|
||||
implicit val txFactory = TransactionFactory(readonly = true)
|
||||
}
|
||||
|
||||
class MyActor extends Actor {
|
||||
import MyActor.txFactory
|
||||
|
||||
def receive = {
|
||||
case message: String =>
|
||||
atomic {
|
||||
// read only transaction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Here's a similar example with an individual transaction factory for each instance of an actor.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.actor._
|
||||
import akka.stm._
|
||||
|
||||
class MyActor extends Actor {
|
||||
implicit val txFactory = TransactionFactory(readonly = true)
|
||||
|
||||
def receive = {
|
||||
case message: String =>
|
||||
atomic {
|
||||
// read only transaction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Transaction lifecycle listeners
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It's possible to have code that will only run on the successful commit of a transaction, or when a transaction aborts. You can do this by adding ``deferred`` or ``compensating`` blocks to a transaction.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
|
||||
atomic {
|
||||
deferred {
|
||||
// executes when transaction commits
|
||||
}
|
||||
compensating {
|
||||
// executes when transaction aborts
|
||||
}
|
||||
}
|
||||
|
||||
Blocking transactions
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can block in a transaction until a condition is met by using an explicit ``retry``. To use ``retry`` you also need to configure the transaction to allow explicit retries.
|
||||
|
||||
Here is an example of using ``retry`` to block until an account has enough money for a withdrawal. This is also an example of using actors and STM together.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
import akka.actor._
|
||||
import akka.util.duration._
|
||||
import akka.event.EventHandler
|
||||
|
||||
type Account = Ref[Double]
|
||||
|
||||
case class Transfer(from: Account, to: Account, amount: Double)
|
||||
|
||||
class Transferer extends Actor {
|
||||
implicit val txFactory = TransactionFactory(blockingAllowed = true, trackReads = true, timeout = 60 seconds)
|
||||
|
||||
def receive = {
|
||||
case Transfer(from, to, amount) =>
|
||||
atomic {
|
||||
if (from.get < amount) {
|
||||
EventHandler.info(this, "not enough money - retrying")
|
||||
retry
|
||||
}
|
||||
EventHandler.info(this, "transferring")
|
||||
from alter (_ - amount)
|
||||
to alter (_ + amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val account1 = Ref(100.0)
|
||||
val account2 = Ref(100.0)
|
||||
|
||||
val transferer = Actor.actorOf(new Transferer)
|
||||
|
||||
transferer ! Transfer(account1, account2, 500.0)
|
||||
// INFO Transferer: not enough money - retrying
|
||||
|
||||
atomic { account1 alter (_ + 2000) }
|
||||
// INFO Transferer: transferring
|
||||
|
||||
atomic { account1.get }
|
||||
// -> 1600.0
|
||||
|
||||
atomic { account2.get }
|
||||
// -> 600.0
|
||||
|
||||
transferer.stop()
|
||||
|
||||
Alternative blocking transactions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can also have two alternative blocking transactions, one of which can succeed first, with ``either-orElse``.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
import akka.actor._
|
||||
import akka.util.duration._
|
||||
import akka.event.EventHandler
|
||||
|
||||
case class Branch(left: Ref[Int], right: Ref[Int], amount: Int)
|
||||
|
||||
class Brancher extends Actor {
|
||||
implicit val txFactory = TransactionFactory(blockingAllowed = true, trackReads = true, timeout = 60 seconds)
|
||||
|
||||
def receive = {
|
||||
case Branch(left, right, amount) =>
|
||||
atomic {
|
||||
either {
|
||||
if (left.get < amount) {
|
||||
EventHandler.info(this, "not enough on left - retrying")
|
||||
retry
|
||||
}
|
||||
log.info("going left")
|
||||
} orElse {
|
||||
if (right.get < amount) {
|
||||
EventHandler.info(this, "not enough on right - retrying")
|
||||
retry
|
||||
}
|
||||
log.info("going right")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val ref1 = Ref(0)
|
||||
val ref2 = Ref(0)
|
||||
|
||||
val brancher = Actor.actorOf(new Brancher)
|
||||
|
||||
brancher ! Branch(ref1, ref2, 1)
|
||||
// INFO Brancher: not enough on left - retrying
|
||||
// INFO Brancher: not enough on right - retrying
|
||||
|
||||
atomic { ref2 alter (_ + 1) }
|
||||
// INFO Brancher: not enough on left - retrying
|
||||
// INFO Brancher: going right
|
||||
|
||||
brancher.stop()
|
||||
|
||||
|
||||
Transactional datastructures
|
||||
----------------------------
|
||||
|
||||
Akka provides two datastructures that are managed by the STM.
|
||||
|
||||
- ``TransactionalMap``
|
||||
- ``TransactionalVector``
|
||||
|
||||
``TransactionalMap`` and ``TransactionalVector`` look like regular mutable datastructures, they even implement the standard Scala 'Map' and 'RandomAccessSeq' interfaces, but they are implemented using persistent datastructures and managed references under the hood. Therefore they are safe to use in a concurrent environment. Underlying TransactionalMap is HashMap, an immutable Map but with near constant time access and modification operations. Similarly ``TransactionalVector`` uses a persistent Vector. See the Persistent Datastructures section below for more details.
|
||||
|
||||
Like managed references, ``TransactionalMap`` and ``TransactionalVector`` can only be modified inside the scope of an STM transaction.
|
||||
|
||||
*IMPORTANT*: There have been some problems reported when using transactional datastructures with 'lazy' initialization. Avoid that.
|
||||
|
||||
Here is how you create these transactional datastructures:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
|
||||
// assuming something like
|
||||
case class User(name: String)
|
||||
case class Address(location: String)
|
||||
|
||||
// using initial values
|
||||
val map = TransactionalMap("bill" -> User("bill"))
|
||||
val vector = TransactionalVector(Address("somewhere"))
|
||||
|
||||
// specifying types
|
||||
val map = TransactionalMap[String, User]
|
||||
val vector = TransactionalVector[Address]
|
||||
|
||||
``TransactionalMap`` and ``TransactionalVector`` wrap persistent datastructures with transactional references and provide a standard Scala interface. This makes them convenient to use.
|
||||
|
||||
Here is an example of using a ``Ref`` and a ``HashMap`` directly:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
import scala.collection.immutable.HashMap
|
||||
|
||||
case class User(name: String)
|
||||
|
||||
val ref = Ref(HashMap[String, User]())
|
||||
|
||||
atomic {
|
||||
val users = ref.get
|
||||
val newUsers = users + ("bill" -> User("bill")) // creates a new HashMap
|
||||
ref.swap(newUsers)
|
||||
}
|
||||
|
||||
atomic {
|
||||
ref.get.apply("bill")
|
||||
}
|
||||
// -> User("bill")
|
||||
|
||||
Here is the same example using ``TransactionalMap``:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.stm._
|
||||
|
||||
case class User(name: String)
|
||||
|
||||
val users = TransactionalMap[String, User]
|
||||
|
||||
atomic {
|
||||
users += "bill" -> User("bill")
|
||||
}
|
||||
|
||||
atomic {
|
||||
users("bill")
|
||||
}
|
||||
// -> User("bill")
|
||||
|
||||
|
||||
Persistent datastructures
|
||||
-------------------------
|
||||
|
||||
Akka's STM should only be used with immutable data. This can be costly if you have large datastructures and are using a naive copy-on-write. In order to make working with immutable datastructures fast enough Scala provides what are called Persistent Datastructures. There are currently two different ones:
|
||||
|
||||
* ``HashMap`` (`scaladoc <http://www.scala-lang.org/api/current/scala/collection/immutable/HashMap.html>`__)
|
||||
* ``Vector`` (`scaladoc <http://www.scala-lang.org/api/current/scala/collection/immutable/Vector.html>`__)
|
||||
|
||||
They are immutable and each update creates a completely new version but they are using clever structural sharing in order to make them almost as fast, for both read and update, as regular mutable datastructures.
|
||||
|
||||
This illustration is taken from Rich Hickey's presentation. Copyright Rich Hickey 2009.
|
||||
|
||||
.. image:: ../images/clojure-trees.png
|
||||
|
||||
|
||||
Ants simulation sample
|
||||
----------------------
|
||||
|
||||
One fun and very enlightening visual demo of STM, actors and transactional references is the `Ant simulation sample <http://github.com/jboner/akka/tree/master/akka-samples/akka-sample-ants/>`_. I encourage you to run it and read through the code since it's a good example of using actors with STM.
|
||||
|
|
@ -1,248 +0,0 @@
|
|||
.. _transactors-scala:
|
||||
|
||||
Transactors (Scala)
|
||||
===================
|
||||
|
||||
.. sidebar:: Contents
|
||||
|
||||
.. contents:: :local:
|
||||
|
||||
Why Transactors?
|
||||
----------------
|
||||
|
||||
Actors are excellent for solving problems where you have many independent processes that can work in isolation and only interact with other Actors through message passing. This model fits many problems. But the actor model is unfortunately a terrible model for implementing truly shared state. E.g. when you need to have consensus and a stable view of state across many components. The classic example is the bank account where clients can deposit and withdraw, in which each operation needs to be atomic. For detailed discussion on the topic see `this JavaOne presentation <http://www.slideshare.net/jboner/state-youre-doing-it-wrong-javaone-2009>`_.
|
||||
|
||||
**STM** on the other hand is excellent for problems where you need consensus and a stable view of the state by providing compositional transactional shared state. Some of the really nice traits of STM are that transactions compose, and it raises the abstraction level from lock-based concurrency.
|
||||
|
||||
Akka's Transactors combine Actors and STM to provide the best of the Actor model (concurrency and asynchronous event-based programming) and STM (compositional transactional shared state) by providing transactional, compositional, asynchronous, event-based message flows.
|
||||
|
||||
If you need Durability then you should not use one of the in-memory data structures but one of the persistent ones.
|
||||
|
||||
Generally, the STM is not needed very often when working with Akka. Some use-cases (that we can think of) are:
|
||||
|
||||
- When you really need composable message flows across many actors updating their **internal local** state but need them to do that atomically in one big transaction. Might not often, but when you do need this then you are screwed without it.
|
||||
- When you want to share a datastructure across actors.
|
||||
- When you need to use the persistence modules.
|
||||
|
||||
Actors and STM
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
You can combine Actors and STM in several ways. An Actor may use STM internally so that particular changes are guaranteed to be atomic. Actors may also share transactional datastructures as the STM provides safe shared state across threads.
|
||||
|
||||
It's also possible to coordinate transactions across Actors or threads so that either the transactions in a set all commit successfully or they all fail. This is the focus of Transactors and the explicit support for coordinated transactions in this section.
|
||||
|
||||
|
||||
Coordinated transactions
|
||||
------------------------
|
||||
|
||||
Akka provides an explicit mechanism for coordinating transactions across Actors. Under the hood it uses a ``CountDownCommitBarrier``, similar to a CountDownLatch.
|
||||
|
||||
Here is an example of coordinating two simple counter Actors so that they both increment together in coordinated transactions. If one of them was to fail to increment, the other would also fail.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.transactor.Coordinated
|
||||
import akka.stm.Ref
|
||||
import akka.actor.{Actor, ActorRef}
|
||||
|
||||
case class Increment(friend: Option[ActorRef] = None)
|
||||
case object GetCount
|
||||
|
||||
class Counter extends Actor {
|
||||
val count = Ref(0)
|
||||
|
||||
def receive = {
|
||||
case coordinated @ Coordinated(Increment(friend)) => {
|
||||
friend foreach (_ ! coordinated(Increment()))
|
||||
coordinated atomic {
|
||||
count alter (_ + 1)
|
||||
}
|
||||
}
|
||||
case GetCount => self.reply(count.get)
|
||||
}
|
||||
}
|
||||
|
||||
val counter1 = Actor.actorOf[Counter]
|
||||
val counter2 = Actor.actorOf[Counter]
|
||||
|
||||
counter1 ! Coordinated(Increment(Some(counter2)))
|
||||
|
||||
...
|
||||
|
||||
(counter1 ? GetCount).as[Int] // Some(1)
|
||||
|
||||
counter1.stop()
|
||||
counter2.stop()
|
||||
|
||||
To start a new coordinated transaction that you will also participate in, just create a ``Coordinated`` object:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
val coordinated = Coordinated()
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
actor ! Coordinated(Message)
|
||||
|
||||
To receive a coordinated message in an actor simply match it in a case statement:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
def receive = {
|
||||
case coordinated @ Coordinated(Message) => ...
|
||||
}
|
||||
|
||||
To include another actor in the same coordinated transaction 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.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
actor ! coordinated(Message)
|
||||
|
||||
To enter the coordinated transaction use the atomic method of the coordinated object:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
coordinated atomic {
|
||||
// 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.
|
||||
|
||||
|
||||
Transactor
|
||||
----------
|
||||
|
||||
Transactors are actors that provide a general pattern for coordinating transactions, using the explicit coordination described above.
|
||||
|
||||
Here's an example of a simple transactor that will join a coordinated transaction:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.transactor.Transactor
|
||||
import akka.stm.Ref
|
||||
|
||||
case object Increment
|
||||
|
||||
class Counter extends Transactor {
|
||||
val count = Ref(0)
|
||||
|
||||
override def atomically = {
|
||||
case Increment => count alter (_ + 1)
|
||||
}
|
||||
}
|
||||
|
||||
You could send this Counter transactor a ``Coordinated(Increment)`` message. If you were to send it just an ``Increment`` message it will create its own ``Coordinated`` (but in this particular case wouldn't be coordinating transactions with any other transactors).
|
||||
|
||||
To coordinate with other transactors override the ``coordinate`` method. The ``coordinate`` method maps a message to a set of ``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 the message to send.
|
||||
|
||||
Example of coordinating an increment:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.transactor.Transactor
|
||||
import akka.stm.Ref
|
||||
import akka.actor.ActorRef
|
||||
|
||||
case object Increment
|
||||
|
||||
class FriendlyCounter(friend: ActorRef) extends Transactor {
|
||||
val count = Ref(0)
|
||||
|
||||
override def coordinate = {
|
||||
case Increment => include(friend)
|
||||
}
|
||||
|
||||
override def atomically = {
|
||||
case Increment => count alter (_ + 1)
|
||||
}
|
||||
}
|
||||
|
||||
Using ``include`` to include more than one transactor:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
override def coordinate = {
|
||||
case Message => include(actor1, actor2, actor3)
|
||||
}
|
||||
|
||||
Using ``sendTo`` to coordinate transactions but pass-on a different message than the one that was received:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
override def coordinate = {
|
||||
case Message => sendTo(someActor -> SomeOtherMessage)
|
||||
case SomeMessage => sendTo(actor1 -> Message1, actor2 -> Message2)
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Coordinating Typed Actors
|
||||
-------------------------
|
||||
|
||||
It's also possible to use coordinated transactions with typed actors. You can explicitly pass around ``Coordinated`` objects, or use built-in support with the ``@Coordinated`` annotation and the ``Coordination.coordinate`` method.
|
||||
|
||||
To specify a method should use coordinated transactions add the ``@Coordinated`` annotation. **Note**: the ``@Coordinated`` annotation only works with methods that return Unit (one-way methods).
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
trait Counter {
|
||||
@Coordinated def increment()
|
||||
def get: Int
|
||||
}
|
||||
|
||||
To coordinate transactions use a ``coordinate`` block:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
coordinate {
|
||||
counter1.increment()
|
||||
counter2.increment()
|
||||
}
|
||||
|
||||
Here's an example of using ``@Coordinated`` with a TypedActor to coordinate increments.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
import akka.actor.TypedActor
|
||||
import akka.stm.Ref
|
||||
import akka.transactor.annotation.Coordinated
|
||||
import akka.transactor.Coordination._
|
||||
|
||||
trait Counter {
|
||||
@Coordinated def increment()
|
||||
def get: Int
|
||||
}
|
||||
|
||||
class CounterImpl extends TypedActor with Counter {
|
||||
val ref = Ref(0)
|
||||
def increment() { ref alter (_ + 1) }
|
||||
def get = ref.get
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
val counter1 = TypedActor.newInstance(classOf[Counter], classOf[CounterImpl])
|
||||
val counter2 = TypedActor.newInstance(classOf[Counter], classOf[CounterImpl])
|
||||
|
||||
coordinate {
|
||||
counter1.increment()
|
||||
counter2.increment()
|
||||
}
|
||||
|
||||
TypedActor.stop(counter1)
|
||||
TypedActor.stop(counter2)
|
||||
|
||||
The ``coordinate`` block will wait for the transactions to complete. If you do not want to wait then you can specify this explicitly:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
coordinate(wait = false) {
|
||||
counter1.increment()
|
||||
counter2.increment()
|
||||
}
|
||||
|
||||
|
|
@ -23,7 +23,6 @@ import akka.japi.Function;
|
|||
//#import-function
|
||||
|
||||
//#import-timeout
|
||||
import akka.util.Duration;
|
||||
import akka.util.Timeout;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
//#import-timeout
|
||||
|
|
@ -86,7 +85,7 @@ public class AgentDocTest {
|
|||
//#send-off
|
||||
|
||||
//#read-await
|
||||
Integer result = agent.await(new Timeout(Duration.create(5, SECONDS)));
|
||||
Integer result = agent.await(new Timeout(5, SECONDS));
|
||||
//#read-await
|
||||
|
||||
assertEquals(result, new Integer(14));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.docs.transactor;
|
||||
|
||||
//#class
|
||||
import akka.actor.*;
|
||||
import akka.transactor.*;
|
||||
import scala.concurrent.stm.*;
|
||||
|
||||
public class CoordinatedCounter extends UntypedActor {
|
||||
private Ref<Integer> count = Stm.ref(0);
|
||||
|
||||
private void increment(InTxn txn) {
|
||||
Integer newValue = count.get(txn) + 1;
|
||||
count.set(newValue, txn);
|
||||
}
|
||||
|
||||
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;
|
||||
if (increment.hasFriend()) {
|
||||
increment.getFriend().tell(coordinated.coordinate(new Increment()));
|
||||
}
|
||||
coordinated.atomic(new Atomically() {
|
||||
public void atomically(InTxn txn) {
|
||||
increment(txn);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if ("GetCount".equals(incoming)) {
|
||||
getSender().tell(count.single().get());
|
||||
}
|
||||
}
|
||||
}
|
||||
//#class
|
||||
27
akka-docs/java/code/akka/docs/transactor/Coordinator.java
Normal file
27
akka-docs/java/code/akka/docs/transactor/Coordinator.java
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.docs.transactor;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.transactor.*;
|
||||
import scala.concurrent.stm.*;
|
||||
|
||||
public class Coordinator extends UntypedActor {
|
||||
public void onReceive(Object incoming) throws Exception {
|
||||
if (incoming instanceof Coordinated) {
|
||||
Coordinated coordinated = (Coordinated) incoming;
|
||||
Object message = coordinated.getMessage();
|
||||
if (message instanceof Message) {
|
||||
//#coordinated-atomic
|
||||
coordinated.atomic(new Atomically() {
|
||||
public void atomically(InTxn txn) {
|
||||
// do something in the coordinated transaction ...
|
||||
}
|
||||
});
|
||||
//#coordinated-atomic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
akka-docs/java/code/akka/docs/transactor/Counter.java
Normal file
28
akka-docs/java/code/akka/docs/transactor/Counter.java
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.docs.transactor;
|
||||
|
||||
//#class
|
||||
import akka.transactor.*;
|
||||
import scala.concurrent.stm.*;
|
||||
|
||||
public class Counter extends UntypedTransactor {
|
||||
Ref<Integer> count = Stm.ref(0);
|
||||
|
||||
public void atomically(InTxn txn, Object message) {
|
||||
if (message instanceof Increment) {
|
||||
Integer newValue = count.get(txn) + 1;
|
||||
count.set(newValue, txn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean normally(Object message) {
|
||||
if ("GetCount".equals(message)) {
|
||||
getSender().tell(count.single().get());
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
//#class
|
||||
|
|
@ -1,13 +1,17 @@
|
|||
package akka.transactor.example;
|
||||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
import akka.transactor.UntypedTransactor;
|
||||
import akka.transactor.SendTo;
|
||||
import akka.stm.Ref;
|
||||
package akka.docs.transactor;
|
||||
|
||||
//#class
|
||||
import akka.actor.*;
|
||||
import akka.transactor.*;
|
||||
import java.util.Set;
|
||||
import scala.concurrent.stm.*;
|
||||
|
||||
public class UntypedCounter extends UntypedTransactor {
|
||||
Ref<Integer> count = new Ref<Integer>(0);
|
||||
public class FriendlyCounter extends UntypedTransactor {
|
||||
Ref<Integer> count = Stm.ref(0);
|
||||
|
||||
@Override public Set<SendTo> coordinate(Object message) {
|
||||
if (message instanceof Increment) {
|
||||
|
|
@ -18,16 +22,18 @@ public class UntypedCounter extends UntypedTransactor {
|
|||
return nobody();
|
||||
}
|
||||
|
||||
public void atomically(Object message) {
|
||||
public void atomically(InTxn txn, Object message) {
|
||||
if (message instanceof Increment) {
|
||||
count.set(count.get() + 1);
|
||||
Integer newValue = count.get(txn) + 1;
|
||||
count.set(newValue, txn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean normally(Object message) {
|
||||
if ("GetCount".equals(message)) {
|
||||
getSender().tell(count.get());
|
||||
getSender().tell(count.single().get());
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
//#class
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
package akka.transactor.example;
|
||||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.docs.transactor;
|
||||
|
||||
//#class
|
||||
import akka.actor.ActorRef;
|
||||
|
||||
public class Increment {
|
||||
|
|
@ -19,3 +24,4 @@ public class Increment {
|
|||
return friend;
|
||||
}
|
||||
}
|
||||
//#class
|
||||
7
akka-docs/java/code/akka/docs/transactor/Message.java
Normal file
7
akka-docs/java/code/akka/docs/transactor/Message.java
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.docs.transactor;
|
||||
|
||||
public class Message {}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.docs.transactor
|
||||
|
||||
import org.scalatest.junit.JUnitWrapperSuite
|
||||
|
||||
class TransactorDocJavaSpec extends JUnitWrapperSuite(
|
||||
"akka.docs.transactor.TransactorDocTest",
|
||||
Thread.currentThread.getContextClassLoader)
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.docs.transactor;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
//#imports
|
||||
import akka.actor.*;
|
||||
import akka.dispatch.Await;
|
||||
import akka.transactor.Coordinated;
|
||||
import akka.util.Duration;
|
||||
import akka.util.Timeout;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
//#imports
|
||||
|
||||
public class TransactorDocTest {
|
||||
|
||||
@Test
|
||||
public void coordinatedExample() {
|
||||
//#coordinated-example
|
||||
ActorSystem system = ActorSystem.create("CoordinatedExample");
|
||||
|
||||
ActorRef counter1 = system.actorOf(new Props(CoordinatedCounter.class));
|
||||
ActorRef counter2 = system.actorOf(new Props(CoordinatedCounter.class));
|
||||
|
||||
Timeout timeout = new Timeout(5, SECONDS);
|
||||
|
||||
counter1.tell(new Coordinated(new Increment(counter2), timeout));
|
||||
|
||||
Integer count = (Integer) Await.result(counter1.ask("GetCount", timeout), timeout.duration());
|
||||
//#coordinated-example
|
||||
|
||||
assertEquals(count, new Integer(1));
|
||||
|
||||
system.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void coordinatedApi() {
|
||||
//#create-coordinated
|
||||
Timeout timeout = new Timeout(5, SECONDS);
|
||||
Coordinated coordinated = new Coordinated(timeout);
|
||||
//#create-coordinated
|
||||
|
||||
ActorSystem system = ActorSystem.create("CoordinatedApi");
|
||||
ActorRef actor = system.actorOf(new Props(Coordinator.class));
|
||||
|
||||
//#send-coordinated
|
||||
actor.tell(new Coordinated(new Message(), timeout));
|
||||
//#send-coordinated
|
||||
|
||||
//#include-coordinated
|
||||
actor.tell(coordinated.coordinate(new Message()));
|
||||
//#include-coordinated
|
||||
|
||||
coordinated.await();
|
||||
|
||||
system.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void counterTransactor() {
|
||||
ActorSystem system = ActorSystem.create("CounterTransactor");
|
||||
ActorRef counter = system.actorOf(new Props(Counter.class));
|
||||
|
||||
Timeout timeout = new Timeout(5, SECONDS);
|
||||
Coordinated coordinated = new Coordinated(timeout);
|
||||
counter.tell(coordinated.coordinate(new Increment()));
|
||||
coordinated.await();
|
||||
|
||||
Integer count = (Integer) Await.result(counter.ask("GetCount", timeout), timeout.duration());
|
||||
assertEquals(count, new Integer(1));
|
||||
|
||||
system.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void friendlyCounterTransactor() {
|
||||
ActorSystem system = ActorSystem.create("FriendlyCounterTransactor");
|
||||
ActorRef friend = system.actorOf(new Props(Counter.class));
|
||||
ActorRef friendlyCounter = system.actorOf(new Props(FriendlyCounter.class));
|
||||
|
||||
Timeout timeout = new Timeout(5, SECONDS);
|
||||
Coordinated coordinated = new Coordinated(timeout);
|
||||
friendlyCounter.tell(coordinated.coordinate(new Increment(friend)));
|
||||
coordinated.await();
|
||||
|
||||
Integer count1 = (Integer) Await.result(friendlyCounter.ask("GetCount", timeout), timeout.duration());
|
||||
assertEquals(count1, new Integer(1));
|
||||
|
||||
Integer count2 = (Integer) Await.result(friend.ask("GetCount", timeout), timeout.duration());
|
||||
assertEquals(count2, new Integer(1));
|
||||
|
||||
system.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ Java API
|
|||
routing
|
||||
remoting
|
||||
serialization
|
||||
stm
|
||||
agents
|
||||
extending-akka
|
||||
transactors
|
||||
extending-akka
|
||||
|
|
|
|||
60
akka-docs/java/stm.rst
Normal file
60
akka-docs/java/stm.rst
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
|
||||
.. _stm-java:
|
||||
|
||||
#####################################
|
||||
Software Transactional Memory (Java)
|
||||
#####################################
|
||||
|
||||
|
||||
Overview of STM
|
||||
===============
|
||||
|
||||
An `STM <http://en.wikipedia.org/wiki/Software_transactional_memory>`_ turns the
|
||||
Java heap into a transactional data set with begin/commit/rollback
|
||||
semantics. Very much like a regular database. It implements the first three
|
||||
letters in `ACID`_; ACI:
|
||||
|
||||
* Atomic
|
||||
* Consistent
|
||||
* Isolated
|
||||
|
||||
.. _ACID: http://en.wikipedia.org/wiki/ACID
|
||||
|
||||
Generally, the STM is not needed very often when working with Akka. Some
|
||||
use-cases (that we can think of) are:
|
||||
|
||||
- When you really need composable message flows across many actors updating
|
||||
their **internal local** state but need them to do that atomically in one big
|
||||
transaction. Might not be often, but when you do need this then you are
|
||||
screwed without it.
|
||||
- When you want to share a datastructure across actors.
|
||||
|
||||
The use of STM in Akka is inspired by the concepts and views in `Clojure`_\'s
|
||||
STM. Please take the time to read `this excellent document`_ about state in
|
||||
clojure and view `this presentation`_ by Rich Hickey (the genius behind
|
||||
Clojure).
|
||||
|
||||
.. _Clojure: http://clojure.org/
|
||||
.. _this excellent document: http://clojure.org/state
|
||||
.. _this presentation: http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey
|
||||
|
||||
|
||||
Scala STM
|
||||
=========
|
||||
|
||||
The STM supported in Akka is `ScalaSTM`_ which will be soon included in the
|
||||
Scala standard library.
|
||||
|
||||
.. _ScalaSTM: http://nbronson.github.com/scala-stm/
|
||||
|
||||
The STM is based on Transactional References (referred to as Refs). Refs are
|
||||
memory cells, holding an (arbitrary) immutable value, that implement CAS
|
||||
(Compare-And-Swap) semantics and are managed and enforced by the STM for
|
||||
coordinated changes across many Refs.
|
||||
|
||||
|
||||
Integration with Actors
|
||||
=======================
|
||||
|
||||
In Akka we've also integrated Actors and STM in :ref:`agents-java` and
|
||||
:ref:`transactors-java`.
|
||||
|
|
@ -1,6 +1,149 @@
|
|||
.. _transactors-java:
|
||||
|
||||
####################
|
||||
Transactors (Java)
|
||||
==================
|
||||
####################
|
||||
|
||||
The Akka Transactors module has not been migrated to Akka 2.0-SNAPSHOT yet.
|
||||
.. sidebar:: Contents
|
||||
|
||||
.. contents:: :local:
|
||||
|
||||
|
||||
Why Transactors?
|
||||
================
|
||||
|
||||
Actors are excellent for solving problems where you have many independent
|
||||
processes that can work in isolation and only interact with other Actors through
|
||||
message passing. This model fits many problems. But the actor model is
|
||||
unfortunately a terrible model for implementing truly shared state. E.g. when
|
||||
you need to have consensus and a stable view of state across many
|
||||
components. The classic example is the bank account where clients can deposit
|
||||
and withdraw, in which each operation needs to be atomic. For detailed
|
||||
discussion on the topic see `this JavaOne presentation
|
||||
<http://www.slideshare.net/jboner/state-youre-doing-it-wrong-javaone-2009>`_.
|
||||
|
||||
STM on the other hand is excellent for problems where you need consensus and a
|
||||
stable view of the state by providing compositional transactional shared
|
||||
state. Some of the really nice traits of STM are that transactions compose, and
|
||||
it raises the abstraction level from lock-based concurrency.
|
||||
|
||||
Akka's Transactors combine Actors and STM to provide the best of the Actor model
|
||||
(concurrency and asynchronous event-based programming) and STM (compositional
|
||||
transactional shared state) by providing transactional, compositional,
|
||||
asynchronous, event-based message flows.
|
||||
|
||||
Generally, the STM is not needed very often when working with Akka. Some
|
||||
use-cases (that we can think of) are:
|
||||
|
||||
- When you really need composable message flows across many actors updating
|
||||
their **internal local** state but need them to do that atomically in one big
|
||||
transaction. Might not be often but when you do need this then you are
|
||||
screwed without it.
|
||||
|
||||
- When you want to share a datastructure across actors.
|
||||
|
||||
|
||||
Actors and STM
|
||||
==============
|
||||
|
||||
You can combine Actors and STM in several ways. An Actor may use STM internally
|
||||
so that particular changes are guaranteed to be atomic. Actors may also share
|
||||
transactional datastructures as the STM provides safe shared state across
|
||||
threads.
|
||||
|
||||
It's also possible to coordinate transactions across Actors or threads so that
|
||||
either the transactions in a set all commit successfully or they all fail. This
|
||||
is the focus of Transactors and the explicit support for coordinated
|
||||
transactions in this section.
|
||||
|
||||
|
||||
Coordinated transactions
|
||||
========================
|
||||
|
||||
Akka provides an explicit mechanism for coordinating transactions across
|
||||
actors. Under the hood it uses a ``CommitBarrier``, similar to a CountDownLatch.
|
||||
|
||||
Here is an example of coordinating two simple counter UntypedActors so that they
|
||||
both increment together in coordinated transactions. If one of them was to fail
|
||||
to increment, the other would also fail.
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/Increment.java#class
|
||||
:language: java
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/CoordinatedCounter.java#class
|
||||
:language: java
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocTest.java#imports
|
||||
:language: java
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocTest.java#coordinated-example
|
||||
:language: java
|
||||
|
||||
To start a new coordinated transaction that you will also participate in, create
|
||||
a ``Coordinated`` object, passing in a ``Timeout``:
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocTest.java#create-coordinated
|
||||
:language: java
|
||||
|
||||
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:
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocTest.java#send-coordinated
|
||||
:language: java
|
||||
|
||||
To include another actor in the same coordinated transaction that you've created
|
||||
or received, use the ``coordinate`` method on that object. This will increment
|
||||
the number of parties involved by one and create a new ``Coordinated`` object to
|
||||
be sent.
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocTest.java#include-coordinated
|
||||
:language: java
|
||||
|
||||
To enter the coordinated transaction use the atomic method of the coordinated
|
||||
object, passing in an ``akka.transactor.Atomically`` object.
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/Coordinator.java#coordinated-atomic
|
||||
:language: java
|
||||
|
||||
The coordinated transaction will wait for the other transactions before
|
||||
committing. If any of the coordinated transactions fail then they all fail.
|
||||
|
||||
|
||||
UntypedTransactor
|
||||
=================
|
||||
|
||||
UntypedTransactors are untyped actors that provide a general pattern for
|
||||
coordinating transactions, using the explicit coordination described above.
|
||||
|
||||
Here's an example of a simple untyped transactor that will join a coordinated
|
||||
transaction:
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/Counter.java#class
|
||||
:language: java
|
||||
|
||||
You could send this Counter transactor a ``Coordinated(Increment)`` message. If
|
||||
you were to send it just an ``Increment`` message it will create its own
|
||||
``Coordinated`` (but in this particular case wouldn't be coordinating
|
||||
transactions with any other transactors).
|
||||
|
||||
To coordinate with other transactors override the ``coordinate`` method. The
|
||||
``coordinate`` method maps a message to a set of ``SendTo`` objects, pairs of
|
||||
``ActorRef`` and a message. You can use the ``include`` and ``sendTo`` methods
|
||||
to easily coordinate with other transactors.
|
||||
|
||||
Here's an example of coordinating an increment, using an untyped transactor,
|
||||
similar to the explicitly coordinated example above.
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/FriendlyCounter.java#class
|
||||
:language: java
|
||||
|
||||
To execute directly before or after the coordinated transaction, override the
|
||||
``before`` and ``after`` methods. 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.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,230 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.docs.transactor
|
||||
|
||||
import akka.actor._
|
||||
import akka.transactor._
|
||||
import akka.util.duration._
|
||||
import akka.util.Timeout
|
||||
import akka.testkit._
|
||||
import scala.concurrent.stm._
|
||||
|
||||
object CoordinatedExample {
|
||||
//#coordinated-example
|
||||
import akka.actor._
|
||||
import akka.transactor._
|
||||
import scala.concurrent.stm._
|
||||
|
||||
case class Increment(friend: Option[ActorRef] = None)
|
||||
case object GetCount
|
||||
|
||||
class Counter extends Actor {
|
||||
val count = Ref(0)
|
||||
|
||||
def receive = {
|
||||
case coordinated @ Coordinated(Increment(friend)) ⇒ {
|
||||
friend foreach (_ ! coordinated(Increment()))
|
||||
coordinated atomic { implicit t ⇒
|
||||
count transform (_ + 1)
|
||||
}
|
||||
}
|
||||
case GetCount ⇒ sender ! count.single.get
|
||||
}
|
||||
}
|
||||
//#coordinated-example
|
||||
}
|
||||
|
||||
object CoordinatedApi {
|
||||
case object Message
|
||||
|
||||
class Coordinator extends Actor {
|
||||
//#receive-coordinated
|
||||
def receive = {
|
||||
case coordinated @ Coordinated(Message) ⇒ {
|
||||
//#coordinated-atomic
|
||||
coordinated atomic { implicit t ⇒
|
||||
// do something in the coordinated transaction ...
|
||||
}
|
||||
//#coordinated-atomic
|
||||
}
|
||||
}
|
||||
//#receive-coordinated
|
||||
}
|
||||
}
|
||||
|
||||
object CounterExample {
|
||||
//#counter-example
|
||||
import akka.transactor._
|
||||
import scala.concurrent.stm._
|
||||
|
||||
case object Increment
|
||||
|
||||
class Counter extends Transactor {
|
||||
val count = Ref(0)
|
||||
|
||||
def atomically = implicit txn ⇒ {
|
||||
case Increment ⇒ count transform (_ + 1)
|
||||
}
|
||||
}
|
||||
//#counter-example
|
||||
}
|
||||
|
||||
object FriendlyCounterExample {
|
||||
//#friendly-counter-example
|
||||
import akka.actor._
|
||||
import akka.transactor._
|
||||
import scala.concurrent.stm._
|
||||
|
||||
case object 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)
|
||||
}
|
||||
}
|
||||
//#friendly-counter-example
|
||||
|
||||
class Friend extends Transactor {
|
||||
val count = Ref(0)
|
||||
|
||||
def atomically = implicit txn ⇒ {
|
||||
case Increment ⇒ count transform (_ + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only checked for compilation
|
||||
object TransactorCoordinate {
|
||||
case object Message
|
||||
case object SomeMessage
|
||||
case object SomeOtherMessage
|
||||
case object OtherMessage
|
||||
case object Message1
|
||||
case object Message2
|
||||
|
||||
class TestCoordinateInclude(actor1: ActorRef, actor2: ActorRef, actor3: ActorRef) extends Transactor {
|
||||
//#coordinate-include
|
||||
override def coordinate = {
|
||||
case Message ⇒ include(actor1, actor2, actor3)
|
||||
}
|
||||
//#coordinate-include
|
||||
|
||||
def atomically = txn ⇒ doNothing
|
||||
}
|
||||
|
||||
class TestCoordinateSendTo(someActor: ActorRef, actor1: ActorRef, actor2: ActorRef) extends Transactor {
|
||||
//#coordinate-sendto
|
||||
override def coordinate = {
|
||||
case SomeMessage ⇒ sendTo(someActor -> SomeOtherMessage)
|
||||
case OtherMessage ⇒ sendTo(actor1 -> Message1, actor2 -> Message2)
|
||||
}
|
||||
//#coordinate-sendto
|
||||
|
||||
def atomically = txn ⇒ doNothing
|
||||
}
|
||||
}
|
||||
|
||||
class TransactorDocSpec extends AkkaSpec {
|
||||
|
||||
"coordinated example" in {
|
||||
import CoordinatedExample._
|
||||
|
||||
//#run-coordinated-example
|
||||
import akka.dispatch.Await
|
||||
import akka.util.duration._
|
||||
import akka.util.Timeout
|
||||
|
||||
val system = ActorSystem("app")
|
||||
|
||||
val counter1 = system.actorOf(Props[Counter], name = "counter1")
|
||||
val counter2 = system.actorOf(Props[Counter], name = "counter2")
|
||||
|
||||
implicit val timeout = Timeout(5 seconds)
|
||||
|
||||
counter1 ! Coordinated(Increment(Some(counter2)))
|
||||
|
||||
val count = Await.result(counter1 ? GetCount, timeout.duration)
|
||||
|
||||
// count == 1
|
||||
//#run-coordinated-example
|
||||
|
||||
count must be === 1
|
||||
|
||||
system.shutdown()
|
||||
}
|
||||
|
||||
"coordinated api" in {
|
||||
import CoordinatedApi._
|
||||
|
||||
//#implicit-timeout
|
||||
import akka.util.duration._
|
||||
import akka.util.Timeout
|
||||
|
||||
implicit val timeout = Timeout(5 seconds)
|
||||
//#implicit-timeout
|
||||
|
||||
//#create-coordinated
|
||||
val coordinated = Coordinated()
|
||||
//#create-coordinated
|
||||
|
||||
val system = ActorSystem("coordinated")
|
||||
val actor = system.actorOf(Props[Coordinator], name = "coordinator")
|
||||
|
||||
//#send-coordinated
|
||||
actor ! Coordinated(Message)
|
||||
//#send-coordinated
|
||||
|
||||
//#include-coordinated
|
||||
actor ! coordinated(Message)
|
||||
//#include-coordinated
|
||||
|
||||
coordinated.await()
|
||||
|
||||
system.shutdown()
|
||||
}
|
||||
|
||||
"counter transactor" in {
|
||||
import CounterExample._
|
||||
|
||||
val system = ActorSystem("transactors")
|
||||
|
||||
lazy val underlyingCounter = new Counter
|
||||
val counter = system.actorOf(Props(underlyingCounter), name = "counter")
|
||||
val coordinated = Coordinated()(Timeout(5 seconds))
|
||||
counter ! coordinated(Increment)
|
||||
coordinated.await()
|
||||
|
||||
underlyingCounter.count.single.get must be === 1
|
||||
|
||||
system.shutdown()
|
||||
}
|
||||
|
||||
"friendly counter transactor" in {
|
||||
import FriendlyCounterExample._
|
||||
|
||||
val system = ActorSystem("transactors")
|
||||
|
||||
lazy val underlyingFriend = new Friend
|
||||
val friend = system.actorOf(Props(underlyingFriend), name = "friend")
|
||||
|
||||
lazy val underlyingFriendlyCounter = new FriendlyCounter(friend)
|
||||
val friendlyCounter = system.actorOf(Props(underlyingFriendlyCounter), name = "friendly")
|
||||
|
||||
val coordinated = Coordinated()(Timeout(5 seconds))
|
||||
friendlyCounter ! coordinated(Increment)
|
||||
coordinated.await()
|
||||
|
||||
underlyingFriendlyCounter.count.single.get must be === 1
|
||||
underlyingFriend.count.single.get must be === 1
|
||||
|
||||
system.shutdown()
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,8 @@ Scala API
|
|||
remoting
|
||||
serialization
|
||||
fsm
|
||||
stm
|
||||
agents
|
||||
transactors
|
||||
testing
|
||||
extending-akka
|
||||
transactors
|
||||
|
|
|
|||
75
akka-docs/scala/stm.rst
Normal file
75
akka-docs/scala/stm.rst
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
|
||||
.. _stm-scala:
|
||||
|
||||
#######################################
|
||||
Software Transactional Memory (Scala)
|
||||
#######################################
|
||||
|
||||
|
||||
Overview of STM
|
||||
===============
|
||||
|
||||
An `STM <http://en.wikipedia.org/wiki/Software_transactional_memory>`_ turns the
|
||||
Java heap into a transactional data set with begin/commit/rollback
|
||||
semantics. Very much like a regular database. It implements the first three
|
||||
letters in `ACID`_; ACI:
|
||||
|
||||
* Atomic
|
||||
* Consistent
|
||||
* Isolated
|
||||
|
||||
.. _ACID: http://en.wikipedia.org/wiki/ACID
|
||||
|
||||
Generally, the STM is not needed very often when working with Akka. Some
|
||||
use-cases (that we can think of) are:
|
||||
|
||||
- When you really need composable message flows across many actors updating
|
||||
their **internal local** state but need them to do that atomically in one big
|
||||
transaction. Might not be often, but when you do need this then you are
|
||||
screwed without it.
|
||||
- When you want to share a datastructure across actors.
|
||||
|
||||
The use of STM in Akka is inspired by the concepts and views in `Clojure`_\'s
|
||||
STM. Please take the time to read `this excellent document`_ about state in
|
||||
clojure and view `this presentation`_ by Rich Hickey (the genius behind
|
||||
Clojure).
|
||||
|
||||
.. _Clojure: http://clojure.org/
|
||||
.. _this excellent document: http://clojure.org/state
|
||||
.. _this presentation: http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickey
|
||||
|
||||
|
||||
Scala STM
|
||||
=========
|
||||
|
||||
The STM supported in Akka is `ScalaSTM`_ which will be soon included in the
|
||||
Scala standard library.
|
||||
|
||||
.. _ScalaSTM: http://nbronson.github.com/scala-stm/
|
||||
|
||||
The STM is based on Transactional References (referred to as Refs). Refs are
|
||||
memory cells, holding an (arbitrary) immutable value, that implement CAS
|
||||
(Compare-And-Swap) semantics and are managed and enforced by the STM for
|
||||
coordinated changes across many Refs.
|
||||
|
||||
|
||||
Persistent Datastructures
|
||||
=========================
|
||||
|
||||
Working with immutable collections can sometimes give bad performance due to
|
||||
extensive copying. Scala provides so-called persistent datastructures which
|
||||
makes working with immutable collections fast. They are immutable but with
|
||||
constant time access and modification. They use structural sharing and an insert
|
||||
or update does not ruin the old structure, hence "persistent". Makes working
|
||||
with immutable composite types fast. The persistent datastructures currently
|
||||
consist of a `Map`_ and `Vector`_.
|
||||
|
||||
.. _Map: http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.Map
|
||||
.. _Vector: http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.Vector
|
||||
|
||||
|
||||
Integration with Actors
|
||||
=======================
|
||||
|
||||
In Akka we've also integrated Actors and STM in :ref:`agents-scala` and
|
||||
:ref:`transactors-scala`.
|
||||
|
|
@ -1,6 +1,159 @@
|
|||
.. _transactors-scala:
|
||||
|
||||
#####################
|
||||
Transactors (Scala)
|
||||
===================
|
||||
#####################
|
||||
|
||||
The Akka Transactors module has not been migrated to Akka 2.0-SNAPSHOT yet.
|
||||
.. sidebar:: Contents
|
||||
|
||||
.. contents:: :local:
|
||||
|
||||
|
||||
Why Transactors?
|
||||
================
|
||||
|
||||
Actors are excellent for solving problems where you have many independent
|
||||
processes that can work in isolation and only interact with other Actors through
|
||||
message passing. This model fits many problems. But the actor model is
|
||||
unfortunately a terrible model for implementing truly shared state. E.g. when
|
||||
you need to have consensus and a stable view of state across many
|
||||
components. The classic example is the bank account where clients can deposit
|
||||
and withdraw, in which each operation needs to be atomic. For detailed
|
||||
discussion on the topic see `this JavaOne presentation
|
||||
<http://www.slideshare.net/jboner/state-youre-doing-it-wrong-javaone-2009>`_.
|
||||
|
||||
STM on the other hand is excellent for problems where you need consensus and a
|
||||
stable view of the state by providing compositional transactional shared
|
||||
state. Some of the really nice traits of STM are that transactions compose, and
|
||||
it raises the abstraction level from lock-based concurrency.
|
||||
|
||||
Akka's Transactors combine Actors and STM to provide the best of the Actor model
|
||||
(concurrency and asynchronous event-based programming) and STM (compositional
|
||||
transactional shared state) by providing transactional, compositional,
|
||||
asynchronous, event-based message flows.
|
||||
|
||||
Generally, the STM is not needed very often when working with Akka. Some
|
||||
use-cases (that we can think of) are:
|
||||
|
||||
- When you really need composable message flows across many actors updating
|
||||
their **internal local** state but need them to do that atomically in one big
|
||||
transaction. Might not be often but when you do need this then you are
|
||||
screwed without it.
|
||||
|
||||
- When you want to share a datastructure across actors.
|
||||
|
||||
|
||||
Actors and STM
|
||||
==============
|
||||
|
||||
You can combine Actors and STM in several ways. An Actor may use STM internally
|
||||
so that particular changes are guaranteed to be atomic. Actors may also share
|
||||
transactional datastructures as the STM provides safe shared state across
|
||||
threads.
|
||||
|
||||
It's also possible to coordinate transactions across Actors or threads so that
|
||||
either the transactions in a set all commit successfully or they all fail. This
|
||||
is the focus of Transactors and the explicit support for coordinated
|
||||
transactions in this section.
|
||||
|
||||
|
||||
Coordinated transactions
|
||||
========================
|
||||
|
||||
Akka provides an explicit mechanism for coordinating transactions across
|
||||
Actors. Under the hood it uses a ``CommitBarrier``, similar to a CountDownLatch.
|
||||
|
||||
Here is an example of coordinating two simple counter Actors so that they both
|
||||
increment together in coordinated transactions. If one of them was to fail to
|
||||
increment, the other would also fail.
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocSpec.scala#coordinated-example
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocSpec.scala#run-coordinated-example
|
||||
|
||||
Note that creating a ``Coordinated`` object requires a ``Timeout`` to be
|
||||
specified for the coordinated transaction. This can be done implicitly, by
|
||||
having an implicit ``Timeout`` in scope, or explicitly, by passing the timeout
|
||||
when creating a a ``Coordinated`` object. Here's an example of specifying an
|
||||
implicit timeout:
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocSpec.scala#implicit-timeout
|
||||
|
||||
To start a new coordinated transaction that you will also participate in, just
|
||||
create a ``Coordinated`` object (this assumes an implicit timeout):
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocSpec.scala#create-coordinated
|
||||
|
||||
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:
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocSpec.scala#send-coordinated
|
||||
|
||||
To receive a coordinated message in an actor simply match it in a case
|
||||
statement:
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocSpec.scala#receive-coordinated
|
||||
:exclude: coordinated-atomic
|
||||
|
||||
To include another actor in the same coordinated transaction 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.
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocSpec.scala#include-coordinated
|
||||
|
||||
To enter the coordinated transaction use the atomic method of the coordinated
|
||||
object:
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocSpec.scala#coordinated-atomic
|
||||
|
||||
The coordinated transaction will wait for the other transactions before
|
||||
committing. If any of the coordinated transactions fail then they all fail.
|
||||
|
||||
|
||||
Transactor
|
||||
==========
|
||||
|
||||
Transactors are actors that provide a general pattern for coordinating
|
||||
transactions, using the explicit coordination described above.
|
||||
|
||||
Here's an example of a simple transactor that will join a coordinated
|
||||
transaction:
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocSpec.scala#counter-example
|
||||
|
||||
You could send this Counter transactor a ``Coordinated(Increment)`` message. If
|
||||
you were to send it just an ``Increment`` message it will create its own
|
||||
``Coordinated`` (but in this particular case wouldn't be coordinating
|
||||
transactions with any other transactors).
|
||||
|
||||
To coordinate with other transactors override the ``coordinate`` method. The
|
||||
``coordinate`` method maps a message to a set of ``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 the message to send.
|
||||
|
||||
Example of coordinating an increment:
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocSpec.scala#friendly-counter-example
|
||||
|
||||
Using ``include`` to include more than one transactor:
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocSpec.scala#coordinate-include
|
||||
|
||||
Using ``sendTo`` to coordinate transactions but pass-on a different message than
|
||||
the one that was received:
|
||||
|
||||
.. includecode:: code/akka/docs/transactor/TransactorDocSpec.scala#coordinate-sendto
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
##################################
|
||||
# Akka STM Reference Config File #
|
||||
##################################
|
||||
|
||||
# This the reference config file has all the default settings.
|
||||
# Make your edits/overrides in your application.conf.
|
||||
|
||||
akka {
|
||||
|
||||
stm {
|
||||
# Should global transactions be fair or non-fair (non fair yield better performance)
|
||||
fair = on
|
||||
max-retries = 1000
|
||||
# Default timeout for blocking transactions and transaction set
|
||||
timeout = 5s
|
||||
write-skew = on
|
||||
blocking-allowed = off
|
||||
interruptible = off
|
||||
speculative = on
|
||||
quick-release = on
|
||||
propagation = "requires"
|
||||
trace-level = "none"
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.stm
|
||||
|
||||
/**
|
||||
* Java-friendly atomic blocks.
|
||||
*
|
||||
* Example usage ''(Java)''
|
||||
*
|
||||
* {{{
|
||||
* import akka.stm.*;
|
||||
*
|
||||
* final Ref<Integer> ref = new Ref<Integer>(0);
|
||||
*
|
||||
* new Atomic() {
|
||||
* public Object atomically() {
|
||||
* return ref.set(1);
|
||||
* }
|
||||
* }.execute();
|
||||
*
|
||||
* // To configure transactions pass a TransactionFactory
|
||||
*
|
||||
* TransactionFactory txFactory = new TransactionFactoryBuilder()
|
||||
* .setReadonly(true)
|
||||
* .build();
|
||||
*
|
||||
* Integer value = new Atomic<Integer>(txFactory) {
|
||||
* public Integer atomically() {
|
||||
* return ref.get();
|
||||
* }
|
||||
* }.execute();
|
||||
* }}}
|
||||
*/
|
||||
abstract class Atomic[T](val factory: TransactionFactory) {
|
||||
def this() = this(DefaultTransactionFactory)
|
||||
def atomically: T
|
||||
def execute: T = atomic(factory)(atomically)
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.stm
|
||||
|
||||
import akka.actor.{ newUuid, Uuid }
|
||||
|
||||
import org.multiverse.transactional.refs.BasicRef
|
||||
|
||||
/**
|
||||
* Common trait for all the transactional objects.
|
||||
*/
|
||||
trait Transactional extends Serializable {
|
||||
val uuid: String
|
||||
}
|
||||
|
||||
/**
|
||||
* Transactional managed reference. See the companion class for more information.
|
||||
*/
|
||||
object Ref {
|
||||
def apply[T]() = new Ref[T]()
|
||||
|
||||
def apply[T](initialValue: T) = new Ref[T](initialValue)
|
||||
|
||||
/**
|
||||
* An implicit conversion that converts a Ref to an Iterable value.
|
||||
*/
|
||||
implicit def ref2Iterable[T](ref: Ref[T]): Iterable[T] = ref.toList
|
||||
}
|
||||
|
||||
/**
|
||||
* Refs (transactional references) are mutable references to values and through
|
||||
* the STM allow the safe sharing of mutable data. Refs separate identity from value.
|
||||
* To ensure safety the value stored in a Ref should be immutable (they can also
|
||||
* contain refs themselves). The value referenced by a Ref can only be accessed
|
||||
* or swapped within a transaction. If a transaction is not available, the call will
|
||||
* be executed in its own transaction.
|
||||
* <br/><br/>
|
||||
*
|
||||
* Creating a Ref ''(Scala)''
|
||||
*
|
||||
* {{{
|
||||
* import akka.stm._
|
||||
*
|
||||
* // giving an initial value
|
||||
* val ref = Ref(0)
|
||||
*
|
||||
* // specifying a type but no initial value
|
||||
* val ref = Ref[Int]
|
||||
* }}}
|
||||
* <br/>
|
||||
*
|
||||
* Creating a Ref ''(Java)''
|
||||
*
|
||||
* {{{
|
||||
* import akka.stm.*;
|
||||
*
|
||||
* // giving an initial value
|
||||
* final Ref<Integer> ref = new Ref<Integer>(0);
|
||||
*
|
||||
* // specifying a type but no initial value
|
||||
* final Ref<Integer> ref = new Ref<Integer>();
|
||||
* }}}
|
||||
*/
|
||||
class Ref[T](initialValue: T) extends BasicRef[T](initialValue) with Transactional {
|
||||
self ⇒
|
||||
|
||||
def this() = this(null.asInstanceOf[T])
|
||||
|
||||
val uuid = newUuid.toString
|
||||
|
||||
def apply() = get
|
||||
|
||||
def update(newValue: T) = set(newValue)
|
||||
|
||||
def swap(newValue: T) = set(newValue)
|
||||
|
||||
def alter(f: T ⇒ T): T = {
|
||||
val value = f(get)
|
||||
set(value)
|
||||
value
|
||||
}
|
||||
|
||||
def opt: Option[T] = Option(get)
|
||||
|
||||
def getOrWait: T = getOrAwait
|
||||
|
||||
def getOrElse(default: ⇒ T): T =
|
||||
if (isNull) default else get
|
||||
|
||||
def isDefined: Boolean = !isNull
|
||||
|
||||
def isEmpty: Boolean = isNull
|
||||
|
||||
def map[B](f: T ⇒ B): Ref[B] =
|
||||
if (isEmpty) Ref[B] else Ref(f(get))
|
||||
|
||||
def flatMap[B](f: T ⇒ Ref[B]): Ref[B] =
|
||||
if (isEmpty) Ref[B] else f(get)
|
||||
|
||||
def filter(p: T ⇒ Boolean): Ref[T] =
|
||||
if (isDefined && p(get)) Ref(get) else Ref[T]
|
||||
|
||||
/**
|
||||
* Necessary to keep from being implicitly converted to Iterable in for comprehensions.
|
||||
*/
|
||||
def withFilter(p: T ⇒ Boolean): WithFilter = new WithFilter(p)
|
||||
|
||||
class WithFilter(p: T ⇒ Boolean) {
|
||||
def map[B](f: T ⇒ B): Ref[B] = self filter p map f
|
||||
def flatMap[B](f: T ⇒ Ref[B]): Ref[B] = self filter p flatMap f
|
||||
def foreach[U](f: T ⇒ U): Unit = self filter p foreach f
|
||||
def withFilter(q: T ⇒ Boolean): WithFilter = new WithFilter(x ⇒ p(x) && q(x))
|
||||
}
|
||||
|
||||
def foreach[U](f: T ⇒ U): Unit =
|
||||
if (isDefined) f(get)
|
||||
|
||||
def elements: Iterator[T] =
|
||||
if (isEmpty) Iterator.empty else Iterator(get)
|
||||
|
||||
def toList: List[T] =
|
||||
if (isEmpty) List() else List(get)
|
||||
|
||||
def toRight[X](left: ⇒ X) =
|
||||
if (isEmpty) Left(left) else Right(get)
|
||||
|
||||
def toLeft[X](right: ⇒ X) =
|
||||
if (isEmpty) Right(right) else Left(get)
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.stm
|
||||
|
||||
import org.multiverse.api.{ StmUtils ⇒ MultiverseStmUtils }
|
||||
import org.multiverse.api.{ Transaction ⇒ MultiverseTransaction }
|
||||
import org.multiverse.templates.{ TransactionalCallable, OrElseTemplate }
|
||||
|
||||
object Stm {
|
||||
/**
|
||||
* Check whether there is an active Multiverse transaction.
|
||||
*/
|
||||
def activeTransaction() = {
|
||||
val tx = org.multiverse.api.ThreadLocalTransaction.getThreadLocalTransaction
|
||||
(tx ne null) && !tx.getStatus.isDead
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the atomic block for local transactions. Automatically imported with:
|
||||
*
|
||||
* {{{
|
||||
* import akka.stm._
|
||||
* }}}
|
||||
* <br/>
|
||||
*
|
||||
* If you need to coordinate transactions across actors see [[akka.stm.Coordinated]].
|
||||
* <br/><br/>
|
||||
*
|
||||
* Example of using the atomic block ''(Scala)''
|
||||
*
|
||||
* {{{
|
||||
* atomic {
|
||||
* // do something within a transaction
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
* @see [[akka.stm.Atomic]] for creating atomic blocks in Java.
|
||||
* @see [[akka.stm.StmUtil]] for useful methods to combine with `atomic`
|
||||
*/
|
||||
trait Stm {
|
||||
val DefaultTransactionFactory = TransactionFactory(DefaultTransactionConfig, "DefaultTransaction")
|
||||
|
||||
def atomic[T](body: ⇒ T)(implicit factory: TransactionFactory = DefaultTransactionFactory): T =
|
||||
atomic(factory)(body)
|
||||
|
||||
def atomic[T](factory: TransactionFactory)(body: ⇒ T): T = {
|
||||
factory.boilerplate.execute(new TransactionalCallable[T]() {
|
||||
def call(mtx: MultiverseTransaction): T = body
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stm utility methods for scheduling transaction lifecycle tasks and for blocking transactions.
|
||||
* Automatically imported with:
|
||||
*
|
||||
* {{{
|
||||
* import akka.stm._
|
||||
* }}}
|
||||
* <br/>
|
||||
*
|
||||
* Schedule a deferred task on the thread local transaction (use within an atomic).
|
||||
* This is executed when the transaction commits.
|
||||
*
|
||||
* {{{
|
||||
* atomic {
|
||||
* deferred {
|
||||
* // executes when transaction successfully commits
|
||||
* }
|
||||
* }
|
||||
* }}}
|
||||
* <br/>
|
||||
*
|
||||
* Schedule a compensating task on the thread local transaction (use within an atomic).
|
||||
* This is executed when the transaction aborts.
|
||||
*
|
||||
* {{{
|
||||
* atomic {
|
||||
* compensating {
|
||||
* // executes when transaction aborts
|
||||
* }
|
||||
* }
|
||||
* }}}
|
||||
* <br/>
|
||||
*
|
||||
* STM retry for blocking transactions (use within an atomic).
|
||||
* Can be used to wait for a condition.
|
||||
*
|
||||
* {{{
|
||||
* atomic {
|
||||
* if (!someCondition) retry
|
||||
* // ...
|
||||
* }
|
||||
* }}}
|
||||
* <br/>
|
||||
*
|
||||
* Use either-orElse to combine two blocking transactions.
|
||||
*
|
||||
* {{{
|
||||
* atomic {
|
||||
* either {
|
||||
* // ...
|
||||
* } orElse {
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
* }}}
|
||||
* <br/>
|
||||
*/
|
||||
trait StmUtil {
|
||||
/**
|
||||
* Schedule a deferred task on the thread local transaction (use within an atomic).
|
||||
* This is executed when the transaction commits.
|
||||
*/
|
||||
def deferred[T](body: ⇒ T): Unit =
|
||||
MultiverseStmUtils.scheduleDeferredTask(new Runnable { def run = body })
|
||||
|
||||
/**
|
||||
* Schedule a compensating task on the thread local transaction (use within an atomic).
|
||||
* This is executed when the transaction aborts.
|
||||
*/
|
||||
def compensating[T](body: ⇒ T): Unit =
|
||||
MultiverseStmUtils.scheduleCompensatingTask(new Runnable { def run = body })
|
||||
|
||||
/**
|
||||
* STM retry for blocking transactions (use within an atomic).
|
||||
* Can be used to wait for a condition.
|
||||
*/
|
||||
def retry() = MultiverseStmUtils.retry
|
||||
|
||||
/**
|
||||
* Use either-orElse to combine two blocking transactions.
|
||||
*/
|
||||
def either[T](firstBody: ⇒ T) = new {
|
||||
def orElse(secondBody: ⇒ T) = new OrElseTemplate[T] {
|
||||
def either(mtx: MultiverseTransaction) = firstBody
|
||||
def orelse(mtx: MultiverseTransaction) = secondBody
|
||||
}.execute()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stm utility methods for using from Java.
|
||||
*/
|
||||
object StmUtils {
|
||||
/**
|
||||
* Schedule a deferred task on the thread local transaction (use within an atomic).
|
||||
* This is executed when the transaction commits.
|
||||
*/
|
||||
def deferred(runnable: Runnable): Unit = MultiverseStmUtils.scheduleDeferredTask(runnable)
|
||||
|
||||
/**
|
||||
* Schedule a compensating task on the thread local transaction (use within an atomic).
|
||||
* This is executed when the transaction aborts.
|
||||
*/
|
||||
def compensating(runnable: Runnable): Unit = MultiverseStmUtils.scheduleCompensatingTask(runnable)
|
||||
|
||||
/**
|
||||
* STM retry for blocking transactions (use within an atomic).
|
||||
* Can be used to wait for a condition.
|
||||
*/
|
||||
def retry = MultiverseStmUtils.retry
|
||||
}
|
||||
|
||||
/**
|
||||
* Use EitherOrElse to combine two blocking transactions (from Java).
|
||||
*/
|
||||
abstract class EitherOrElse[T] extends OrElseTemplate[T] {
|
||||
def either(mtx: MultiverseTransaction) = either
|
||||
def orelse(mtx: MultiverseTransaction) = orElse
|
||||
|
||||
def either: T
|
||||
def orElse: T
|
||||
}
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.stm
|
||||
|
||||
import java.lang.{ Boolean ⇒ JBoolean }
|
||||
|
||||
import akka.util.Duration
|
||||
|
||||
import org.multiverse.api.GlobalStmInstance.getGlobalStmInstance
|
||||
import org.multiverse.stms.alpha.AlphaStm
|
||||
import org.multiverse.templates.TransactionBoilerplate
|
||||
import org.multiverse.api.PropagationLevel
|
||||
import org.multiverse.api.{ TraceLevel ⇒ MTraceLevel }
|
||||
|
||||
/**
|
||||
* For configuring multiverse transactions.
|
||||
*/
|
||||
object TransactionConfig {
|
||||
object Default {
|
||||
// note: null values are so that we can default to Multiverse inference when not set
|
||||
val FamilyName = "DefaultTransaction"
|
||||
val Readonly = null.asInstanceOf[JBoolean]
|
||||
val MaxRetries = 1000
|
||||
val Timeout = Duration(5, "seconds")
|
||||
val TrackReads = null.asInstanceOf[JBoolean]
|
||||
val WriteSkew = true
|
||||
val BlockingAllowed = false
|
||||
val Interruptible = false
|
||||
val Speculative = true
|
||||
val QuickRelease = true
|
||||
val Propagation = PropagationLevel.Requires
|
||||
val TraceLevel = MTraceLevel.none
|
||||
}
|
||||
|
||||
/**
|
||||
* For configuring multiverse transactions.
|
||||
*
|
||||
* @param familyName Family name for transactions. Useful for debugging.
|
||||
* @param readonly Sets transaction as readonly. Readonly transactions are cheaper.
|
||||
* @param maxRetries The maximum number of times a transaction will retry.
|
||||
* @param timeout The maximum time a transaction will block for.
|
||||
* @param trackReads Whether all reads should be tracked. Needed for blocking operations.
|
||||
* @param writeSkew Whether writeskew is allowed. Disable with care.
|
||||
* @param blockingAllowed Whether explicit retries are allowed.
|
||||
* @param interruptible Whether a blocking transaction can be interrupted.
|
||||
* @param speculative Whether speculative configuration should be enabled.
|
||||
* @param quickRelease Whether locks should be released as quickly as possible (before whole commit).
|
||||
* @param propagation For controlling how nested transactions behave.
|
||||
* @param traceLevel Transaction trace level.
|
||||
*/
|
||||
def apply(familyName: String = Default.FamilyName,
|
||||
readonly: JBoolean = Default.Readonly,
|
||||
maxRetries: Int = Default.MaxRetries,
|
||||
timeout: Duration = Default.Timeout,
|
||||
trackReads: JBoolean = Default.TrackReads,
|
||||
writeSkew: Boolean = Default.WriteSkew,
|
||||
blockingAllowed: Boolean = Default.BlockingAllowed,
|
||||
interruptible: Boolean = Default.Interruptible,
|
||||
speculative: Boolean = Default.Speculative,
|
||||
quickRelease: Boolean = Default.QuickRelease,
|
||||
propagation: PropagationLevel = Default.Propagation,
|
||||
traceLevel: MTraceLevel = Default.TraceLevel) = {
|
||||
new TransactionConfig(familyName, readonly, maxRetries, timeout, trackReads, writeSkew, blockingAllowed,
|
||||
interruptible, speculative, quickRelease, propagation, traceLevel)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For configuring multiverse transactions.
|
||||
*
|
||||
* <p>familyName - Family name for transactions. Useful for debugging.
|
||||
* <p>readonly - Sets transaction as readonly. Readonly transactions are cheaper.
|
||||
* <p>maxRetries - The maximum number of times a transaction will retry.
|
||||
* <p>timeout - The maximum time a transaction will block for.
|
||||
* <p>trackReads - Whether all reads should be tracked. Needed for blocking operations.
|
||||
* <p>writeSkew - Whether writeskew is allowed. Disable with care.
|
||||
* <p>blockingAllowed - Whether explicit retries are allowed.
|
||||
* <p>interruptible - Whether a blocking transaction can be interrupted.
|
||||
* <p>speculative - Whether speculative configuration should be enabled.
|
||||
* <p>quickRelease - Whether locks should be released as quickly as possible (before whole commit).
|
||||
* <p>propagation - For controlling how nested transactions behave.
|
||||
* <p>traceLevel - Transaction trace level.
|
||||
*/
|
||||
class TransactionConfig(val familyName: String = TransactionConfig.Default.FamilyName,
|
||||
val readonly: JBoolean = TransactionConfig.Default.Readonly,
|
||||
val maxRetries: Int = TransactionConfig.Default.MaxRetries,
|
||||
val timeout: Duration = TransactionConfig.Default.Timeout,
|
||||
val trackReads: JBoolean = TransactionConfig.Default.TrackReads,
|
||||
val writeSkew: Boolean = TransactionConfig.Default.WriteSkew,
|
||||
val blockingAllowed: Boolean = TransactionConfig.Default.BlockingAllowed,
|
||||
val interruptible: Boolean = TransactionConfig.Default.Interruptible,
|
||||
val speculative: Boolean = TransactionConfig.Default.Speculative,
|
||||
val quickRelease: Boolean = TransactionConfig.Default.QuickRelease,
|
||||
val propagation: PropagationLevel = TransactionConfig.Default.Propagation,
|
||||
val traceLevel: MTraceLevel = TransactionConfig.Default.TraceLevel)
|
||||
|
||||
object DefaultTransactionConfig extends TransactionConfig
|
||||
|
||||
/**
|
||||
* Wrapper for transaction config, factory, and boilerplate. Used by atomic.
|
||||
*/
|
||||
object TransactionFactory {
|
||||
def apply(config: TransactionConfig) = new TransactionFactory(config)
|
||||
|
||||
def apply(config: TransactionConfig, defaultName: String) = new TransactionFactory(config, defaultName)
|
||||
|
||||
def apply(familyName: String = TransactionConfig.Default.FamilyName,
|
||||
readonly: JBoolean = TransactionConfig.Default.Readonly,
|
||||
maxRetries: Int = TransactionConfig.Default.MaxRetries,
|
||||
timeout: Duration = TransactionConfig.Default.Timeout,
|
||||
trackReads: JBoolean = TransactionConfig.Default.TrackReads,
|
||||
writeSkew: Boolean = TransactionConfig.Default.WriteSkew,
|
||||
blockingAllowed: Boolean = TransactionConfig.Default.BlockingAllowed,
|
||||
interruptible: Boolean = TransactionConfig.Default.Interruptible,
|
||||
speculative: Boolean = TransactionConfig.Default.Speculative,
|
||||
quickRelease: Boolean = TransactionConfig.Default.QuickRelease,
|
||||
propagation: PropagationLevel = TransactionConfig.Default.Propagation,
|
||||
traceLevel: MTraceLevel = TransactionConfig.Default.TraceLevel) = {
|
||||
val config = new TransactionConfig(
|
||||
familyName, readonly, maxRetries, timeout, trackReads, writeSkew, blockingAllowed,
|
||||
interruptible, speculative, quickRelease, propagation, traceLevel)
|
||||
new TransactionFactory(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for transaction config, factory, and boilerplate. Used by atomic.
|
||||
* Can be passed to atomic implicitly or explicitly.
|
||||
*
|
||||
* {{{
|
||||
* implicit val txFactory = TransactionFactory(readonly = true)
|
||||
* ...
|
||||
* atomic {
|
||||
* // do something within a readonly transaction
|
||||
* }
|
||||
* }}}
|
||||
*
|
||||
* Can be created at different levels as needed. For example: as an implicit object
|
||||
* used throughout a package, as a static implicit val within a singleton object and
|
||||
* imported where needed, or as an implicit val within each instance of a class.
|
||||
*
|
||||
* If no explicit transaction factory is passed to atomic and there is no implicit
|
||||
* transaction factory in scope, then a default transaction factory is used.
|
||||
*
|
||||
* @see [[akka.stm.TransactionConfig]] for configuration options.
|
||||
*/
|
||||
class TransactionFactory(
|
||||
val config: TransactionConfig = DefaultTransactionConfig,
|
||||
defaultName: String = TransactionConfig.Default.FamilyName) { self ⇒
|
||||
|
||||
// use the config family name if it's been set, otherwise defaultName - used by actors to set class name as default
|
||||
val familyName = if (config.familyName != TransactionConfig.Default.FamilyName) config.familyName else defaultName
|
||||
|
||||
val factory = {
|
||||
var builder = (getGlobalStmInstance().asInstanceOf[AlphaStm].getTransactionFactoryBuilder()
|
||||
.setFamilyName(familyName)
|
||||
.setMaxRetries(config.maxRetries)
|
||||
.setTimeoutNs(config.timeout.toNanos)
|
||||
.setWriteSkewAllowed(config.writeSkew)
|
||||
.setExplicitRetryAllowed(config.blockingAllowed)
|
||||
.setInterruptible(config.interruptible)
|
||||
.setSpeculativeConfigurationEnabled(config.speculative)
|
||||
.setQuickReleaseEnabled(config.quickRelease)
|
||||
.setPropagationLevel(config.propagation)
|
||||
.setTraceLevel(config.traceLevel))
|
||||
|
||||
if (config.readonly ne null) {
|
||||
builder = builder.setReadonly(config.readonly.booleanValue)
|
||||
} // otherwise default to Multiverse inference
|
||||
|
||||
if (config.trackReads ne null) {
|
||||
builder = builder.setReadTrackingEnabled(config.trackReads.booleanValue)
|
||||
} // otherwise default to Multiverse inference
|
||||
|
||||
builder.build()
|
||||
}
|
||||
|
||||
val boilerplate = new TransactionBoilerplate(factory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping to Multiverse PropagationLevel.
|
||||
*/
|
||||
object Propagation {
|
||||
val RequiresNew = PropagationLevel.RequiresNew
|
||||
val Mandatory = PropagationLevel.Mandatory
|
||||
val Requires = PropagationLevel.Requires
|
||||
val Supports = PropagationLevel.Supports
|
||||
val Never = PropagationLevel.Never
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapping to Multiverse TraceLevel.
|
||||
*/
|
||||
object TraceLevel {
|
||||
val None = MTraceLevel.none
|
||||
val Coarse = MTraceLevel.course // mispelling?
|
||||
val Course = MTraceLevel.course
|
||||
val Fine = MTraceLevel.fine
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.stm
|
||||
|
||||
import java.lang.{ Boolean ⇒ JBoolean }
|
||||
|
||||
import akka.util.Duration
|
||||
|
||||
import org.multiverse.api.{ TraceLevel ⇒ MTraceLevel }
|
||||
import org.multiverse.api.PropagationLevel
|
||||
|
||||
/**
|
||||
* For more easily creating TransactionConfig from Java.
|
||||
*/
|
||||
class TransactionConfigBuilder {
|
||||
var familyName: String = TransactionConfig.Default.FamilyName
|
||||
var readonly: JBoolean = TransactionConfig.Default.Readonly
|
||||
var maxRetries: Int = TransactionConfig.Default.MaxRetries
|
||||
var timeout: Duration = TransactionConfig.Default.Timeout
|
||||
var trackReads: JBoolean = TransactionConfig.Default.TrackReads
|
||||
var writeSkew: Boolean = TransactionConfig.Default.WriteSkew
|
||||
var blockingAllowed: Boolean = TransactionConfig.Default.BlockingAllowed
|
||||
var interruptible: Boolean = TransactionConfig.Default.Interruptible
|
||||
var speculative: Boolean = TransactionConfig.Default.Speculative
|
||||
var quickRelease: Boolean = TransactionConfig.Default.QuickRelease
|
||||
var propagation: PropagationLevel = TransactionConfig.Default.Propagation
|
||||
var traceLevel: MTraceLevel = TransactionConfig.Default.TraceLevel
|
||||
|
||||
def setFamilyName(familyName: String) = { this.familyName = familyName; this }
|
||||
def setReadonly(readonly: JBoolean) = { this.readonly = readonly; this }
|
||||
def setMaxRetries(maxRetries: Int) = { this.maxRetries = maxRetries; this }
|
||||
def setTimeout(timeout: Duration) = { this.timeout = timeout; this }
|
||||
def setTrackReads(trackReads: JBoolean) = { this.trackReads = trackReads; this }
|
||||
def setWriteSkew(writeSkew: Boolean) = { this.writeSkew = writeSkew; this }
|
||||
def setBlockingAllowed(blockingAllowed: Boolean) = { this.blockingAllowed = blockingAllowed; this }
|
||||
def setInterruptible(interruptible: Boolean) = { this.interruptible = interruptible; this }
|
||||
def setSpeculative(speculative: Boolean) = { this.speculative = speculative; this }
|
||||
def setQuickRelease(quickRelease: Boolean) = { this.quickRelease = quickRelease; this }
|
||||
def setPropagation(propagation: PropagationLevel) = { this.propagation = propagation; this }
|
||||
def setTraceLevel(traceLevel: MTraceLevel) = { this.traceLevel = traceLevel; this }
|
||||
|
||||
def build() = new TransactionConfig(
|
||||
familyName, readonly, maxRetries, timeout, trackReads, writeSkew, blockingAllowed,
|
||||
interruptible, speculative, quickRelease, propagation, traceLevel)
|
||||
}
|
||||
|
||||
/**
|
||||
* For more easily creating TransactionFactory from Java.
|
||||
*/
|
||||
class TransactionFactoryBuilder {
|
||||
var familyName: String = TransactionConfig.Default.FamilyName
|
||||
var readonly: JBoolean = TransactionConfig.Default.Readonly
|
||||
var maxRetries: Int = TransactionConfig.Default.MaxRetries
|
||||
var timeout: Duration = TransactionConfig.Default.Timeout
|
||||
var trackReads: JBoolean = TransactionConfig.Default.TrackReads
|
||||
var writeSkew: Boolean = TransactionConfig.Default.WriteSkew
|
||||
var blockingAllowed: Boolean = TransactionConfig.Default.BlockingAllowed
|
||||
var interruptible: Boolean = TransactionConfig.Default.Interruptible
|
||||
var speculative: Boolean = TransactionConfig.Default.Speculative
|
||||
var quickRelease: Boolean = TransactionConfig.Default.QuickRelease
|
||||
var propagation: PropagationLevel = TransactionConfig.Default.Propagation
|
||||
var traceLevel: MTraceLevel = TransactionConfig.Default.TraceLevel
|
||||
|
||||
def setFamilyName(familyName: String) = { this.familyName = familyName; this }
|
||||
def setReadonly(readonly: JBoolean) = { this.readonly = readonly; this }
|
||||
def setMaxRetries(maxRetries: Int) = { this.maxRetries = maxRetries; this }
|
||||
def setTimeout(timeout: Duration) = { this.timeout = timeout; this }
|
||||
def setTrackReads(trackReads: JBoolean) = { this.trackReads = trackReads; this }
|
||||
def setWriteSkew(writeSkew: Boolean) = { this.writeSkew = writeSkew; this }
|
||||
def setBlockingAllowed(blockingAllowed: Boolean) = { this.blockingAllowed = blockingAllowed; this }
|
||||
def setInterruptible(interruptible: Boolean) = { this.interruptible = interruptible; this }
|
||||
def setSpeculative(speculative: Boolean) = { this.speculative = speculative; this }
|
||||
def setQuickRelease(quickRelease: Boolean) = { this.quickRelease = quickRelease; this }
|
||||
def setPropagation(propagation: PropagationLevel) = { this.propagation = propagation; this }
|
||||
def setTraceLevel(traceLevel: MTraceLevel) = { this.traceLevel = traceLevel; this }
|
||||
|
||||
def build() = {
|
||||
val config = new TransactionConfig(
|
||||
familyName, readonly, maxRetries, timeout, trackReads, writeSkew, blockingAllowed,
|
||||
interruptible, speculative, quickRelease, propagation, traceLevel)
|
||||
new TransactionFactory(config)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.stm
|
||||
|
||||
import scala.collection.immutable.HashMap
|
||||
|
||||
import akka.actor.{ newUuid }
|
||||
|
||||
/**
|
||||
* Transactional map that implements the mutable Map interface with an underlying Ref and HashMap.
|
||||
*/
|
||||
object TransactionalMap {
|
||||
def apply[K, V]() = new TransactionalMap[K, V]()
|
||||
|
||||
def apply[K, V](pairs: (K, V)*) = new TransactionalMap(HashMap(pairs: _*))
|
||||
}
|
||||
|
||||
/**
|
||||
* Transactional map that implements the mutable Map interface with an underlying Ref and HashMap.
|
||||
*
|
||||
* TransactionalMap and TransactionalVector look like regular mutable datastructures, they even
|
||||
* implement the standard Scala 'Map' and 'IndexedSeq' interfaces, but they are implemented using
|
||||
* persistent datastructures and managed references under the hood. Therefore they are safe to use
|
||||
* in a concurrent environment through the STM. Underlying TransactionalMap is HashMap, an immutable
|
||||
* Map but with near constant time access and modification operations.
|
||||
*
|
||||
* From Scala you can use TMap as a shorter alias for TransactionalMap.
|
||||
*/
|
||||
class TransactionalMap[K, V](initialValue: HashMap[K, V]) extends Transactional with scala.collection.mutable.Map[K, V] {
|
||||
def this() = this(HashMap[K, V]())
|
||||
|
||||
val uuid = newUuid.toString
|
||||
|
||||
private[this] val ref = Ref(initialValue)
|
||||
|
||||
def -=(key: K) = {
|
||||
remove(key)
|
||||
this
|
||||
}
|
||||
|
||||
def +=(key: K, value: V) = put(key, value)
|
||||
|
||||
def +=(kv: (K, V)) = {
|
||||
put(kv._1, kv._2)
|
||||
this
|
||||
}
|
||||
|
||||
override def remove(key: K) = {
|
||||
val map = ref.get
|
||||
val oldValue = map.get(key)
|
||||
ref.swap(ref.get - key)
|
||||
oldValue
|
||||
}
|
||||
|
||||
def get(key: K): Option[V] = ref.get.get(key)
|
||||
|
||||
override def put(key: K, value: V): Option[V] = {
|
||||
val map = ref.get
|
||||
val oldValue = map.get(key)
|
||||
ref.swap(map.updated(key, value))
|
||||
oldValue
|
||||
}
|
||||
|
||||
override def update(key: K, value: V) = {
|
||||
val map = ref.get
|
||||
val oldValue = map.get(key)
|
||||
ref.swap(map.updated(key, value))
|
||||
}
|
||||
|
||||
def iterator = ref.get.iterator
|
||||
|
||||
override def elements: Iterator[(K, V)] = ref.get.iterator
|
||||
|
||||
override def contains(key: K): Boolean = ref.get.contains(key)
|
||||
|
||||
override def clear = ref.swap(HashMap[K, V]())
|
||||
|
||||
override def size: Int = ref.get.size
|
||||
|
||||
override def hashCode: Int = System.identityHashCode(this);
|
||||
|
||||
override def equals(other: Any): Boolean =
|
||||
other.isInstanceOf[TransactionalMap[_, _]] &&
|
||||
other.hashCode == hashCode
|
||||
|
||||
override def toString = if (Stm.activeTransaction) super.toString else "<TransactionalMap>"
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.stm
|
||||
|
||||
import scala.collection.immutable.Vector
|
||||
|
||||
import akka.actor.newUuid
|
||||
|
||||
/**
|
||||
* Transactional vector that implements the IndexedSeq interface with an underlying Ref and Vector.
|
||||
*/
|
||||
object TransactionalVector {
|
||||
def apply[T]() = new TransactionalVector[T]()
|
||||
|
||||
def apply[T](elems: T*) = new TransactionalVector(Vector(elems: _*))
|
||||
}
|
||||
|
||||
/**
|
||||
* Transactional vector that implements the IndexedSeq interface with an underlying Ref and Vector.
|
||||
*
|
||||
* TransactionalMap and TransactionalVector look like regular mutable datastructures, they even
|
||||
* implement the standard Scala 'Map' and 'IndexedSeq' interfaces, but they are implemented using
|
||||
* persistent datastructures and managed references under the hood. Therefore they are safe to use
|
||||
* in a concurrent environment through the STM. Underlying TransactionalVector is Vector, an immutable
|
||||
* sequence but with near constant time access and modification operations.
|
||||
*
|
||||
* From Scala you can use TVector as a shorter alias for TransactionalVector.
|
||||
*/
|
||||
class TransactionalVector[T](initialValue: Vector[T]) extends Transactional with IndexedSeq[T] {
|
||||
def this() = this(Vector[T]())
|
||||
|
||||
val uuid = newUuid.toString
|
||||
|
||||
private[this] val ref = Ref(initialValue)
|
||||
|
||||
def clear = ref.swap(Vector[T]())
|
||||
|
||||
def +(elem: T) = add(elem)
|
||||
|
||||
def add(elem: T) = ref.swap(ref.get :+ elem)
|
||||
|
||||
def get(index: Int): T = ref.get.apply(index)
|
||||
|
||||
/**
|
||||
* Removes the <i>tail</i> element of this vector.
|
||||
*/
|
||||
def pop = ref.swap(ref.get.dropRight(1))
|
||||
|
||||
def update(index: Int, elem: T) = ref.swap(ref.get.updated(index, elem))
|
||||
|
||||
def length: Int = ref.get.length
|
||||
|
||||
def apply(index: Int): T = ref.get.apply(index)
|
||||
|
||||
override def hashCode: Int = System.identityHashCode(this);
|
||||
|
||||
override def equals(other: Any): Boolean =
|
||||
other.isInstanceOf[TransactionalVector[_]] &&
|
||||
other.hashCode == hashCode
|
||||
|
||||
override def toString = if (Stm.activeTransaction) super.toString else "<TransactionalVector>"
|
||||
}
|
||||
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka
|
||||
|
||||
/**
|
||||
* For easily importing everything needed for STM.
|
||||
*/
|
||||
package object stm extends akka.stm.Stm with akka.stm.StmUtil {
|
||||
|
||||
// Shorter aliases for transactional map and vector
|
||||
|
||||
type TMap[K, V] = akka.stm.TransactionalMap[K, V]
|
||||
val TMap = akka.stm.TransactionalMap
|
||||
|
||||
type TVector[T] = akka.stm.TransactionalVector[T]
|
||||
val TVector = akka.stm.TransactionalVector
|
||||
|
||||
// Multiverse primitive refs
|
||||
|
||||
type BooleanRef = org.multiverse.transactional.refs.BooleanRef
|
||||
type ByteRef = org.multiverse.transactional.refs.ByteRef
|
||||
type CharRef = org.multiverse.transactional.refs.CharRef
|
||||
type DoubleRef = org.multiverse.transactional.refs.DoubleRef
|
||||
type FloatRef = org.multiverse.transactional.refs.FloatRef
|
||||
type IntRef = org.multiverse.transactional.refs.IntRef
|
||||
type LongRef = org.multiverse.transactional.refs.LongRef
|
||||
type ShortRef = org.multiverse.transactional.refs.ShortRef
|
||||
|
||||
// Multiverse transactional datastructures
|
||||
|
||||
type TransactionalReferenceArray[T] = org.multiverse.transactional.arrays.TransactionalReferenceArray[T]
|
||||
type TransactionalThreadPoolExecutor = org.multiverse.transactional.executors.TransactionalThreadPoolExecutor
|
||||
|
||||
// These won't compile:
|
||||
// Transaction arg is added after varargs with byte code rewriting but Scala compiler doesn't allow this
|
||||
|
||||
// type TransactionalArrayList[T] = org.multiverse.transactional.collections.TransactionalArrayList[T]
|
||||
// type TransactionalLinkedList[T] = org.multiverse.transactional.collections.TransactionalLinkedList[T]
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor
|
||||
|
||||
import akka.stm.TransactionFactory
|
||||
|
||||
/**
|
||||
* For Java-friendly coordinated atomic blocks.
|
||||
*
|
||||
* Similar to [[akka.stm.Atomic]] but used to pass a block to Coordinated.atomic
|
||||
* or to Coordination.coordinate.
|
||||
*
|
||||
* @see [[akka.transactor.Coordinated]]
|
||||
* @see [[akka.transactor.Coordination]]
|
||||
*/
|
||||
abstract class Atomically(val factory: TransactionFactory) {
|
||||
def this() = this(Coordinated.DefaultFactory)
|
||||
def atomically: Unit
|
||||
}
|
||||
|
|
@ -1,199 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor
|
||||
|
||||
import akka.AkkaException
|
||||
import akka.stm.{ Atomic, DefaultTransactionConfig, TransactionFactory }
|
||||
|
||||
import org.multiverse.commitbarriers.CountDownCommitBarrier
|
||||
import org.multiverse.templates.TransactionalCallable
|
||||
import akka.actor.ActorTimeoutException
|
||||
import org.multiverse.api.{ TransactionConfiguration, Transaction ⇒ MultiverseTransaction }
|
||||
import org.multiverse.api.exceptions.ControlFlowError
|
||||
|
||||
/**
|
||||
* Akka-specific exception for coordinated transactions.
|
||||
*/
|
||||
class CoordinatedTransactionException(message: String, cause: Throwable = null) extends AkkaException(message, cause) {
|
||||
def this(msg: String) = this(msg, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinated transactions across actors.
|
||||
*/
|
||||
object Coordinated {
|
||||
val DefaultFactory = TransactionFactory(DefaultTransactionConfig, "DefaultCoordinatedTransaction")
|
||||
|
||||
def apply(message: Any = null) = new Coordinated(message, createBarrier)
|
||||
|
||||
def unapply(c: Coordinated): Option[Any] = Some(c.message)
|
||||
|
||||
def createBarrier = new CountDownCommitBarrier(1, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* `Coordinated` is a message wrapper that adds a `CountDownCommitBarrier` for explicitly
|
||||
* coordinating transactions across actors or threads.
|
||||
*
|
||||
* Creating a `Coordinated` will create a count down barrier with initially one member.
|
||||
* For each member in the coordination set a transaction is expected to be created using
|
||||
* the coordinated atomic method. The number of included parties 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 {
|
||||
* // 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.actor.Transactor]] for an actor that implements coordinated transactions
|
||||
*/
|
||||
class Coordinated(val message: Any, barrier: CountDownCommitBarrier) {
|
||||
|
||||
// Java API constructors
|
||||
def this(message: Any) = this(message, Coordinated.createBarrier)
|
||||
|
||||
def this() = this(null, Coordinated.createBarrier)
|
||||
|
||||
/**
|
||||
* Create a new Coordinated object and increment the number of parties by one.
|
||||
* Use this method to ''pass on'' the coordination.
|
||||
*/
|
||||
def apply(msg: Any) = {
|
||||
barrier.incParties(1)
|
||||
new Coordinated(msg, barrier)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Coordinated object but *do not* increment the number of parties by one.
|
||||
* Only use this method if you know this is what you need.
|
||||
*/
|
||||
def noIncrement(msg: Any) = new Coordinated(msg, barrier)
|
||||
|
||||
/**
|
||||
* Java API: get the message for this Coordinated.
|
||||
*/
|
||||
def getMessage() = message
|
||||
|
||||
/**
|
||||
* Java API: create a new Coordinated object and increment the number of parties by one.
|
||||
* Use this method to ''pass on'' the coordination.
|
||||
*/
|
||||
def coordinate(msg: Any) = apply(msg)
|
||||
|
||||
/**
|
||||
* Delimits the coordinated transaction. The transaction will wait for all other transactions
|
||||
* in this coordination before committing. The timeout is specified by the transaction factory.
|
||||
*
|
||||
* @throws ActorTimeoutException if the coordinated transaction times out.
|
||||
*/
|
||||
def atomic[T](body: ⇒ T)(implicit factory: TransactionFactory = Coordinated.DefaultFactory): T =
|
||||
atomic(factory)(body)
|
||||
|
||||
/**
|
||||
* Delimits the coordinated transaction. The transaction will wait for all other transactions
|
||||
* in this coordination before committing. The timeout is specified by the transaction factory.
|
||||
*
|
||||
* @throws ActorTimeoutException if the coordinated transaction times out.
|
||||
*/
|
||||
def atomic[T](factory: TransactionFactory)(body: ⇒ T): T = {
|
||||
factory.boilerplate.execute(new TransactionalCallable[T]() {
|
||||
def call(mtx: MultiverseTransaction): T = {
|
||||
val result = try {
|
||||
body
|
||||
} catch {
|
||||
case e: ControlFlowError ⇒ throw e
|
||||
case e: Exception ⇒ {
|
||||
barrier.abort()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
val timeout = factory.config.timeout
|
||||
val success = try {
|
||||
barrier.tryJoinCommit(mtx, timeout.length, timeout.unit)
|
||||
} catch {
|
||||
case e: IllegalStateException ⇒ {
|
||||
val config: TransactionConfiguration = mtx.getConfiguration
|
||||
throw new CoordinatedTransactionException("Coordinated transaction [" + config.getFamilyName + "] aborted", e)
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
val config: TransactionConfiguration = mtx.getConfiguration
|
||||
throw new ActorTimeoutException(
|
||||
"Failed to complete coordinated transaction [" + config.getFamilyName + "] " +
|
||||
"with a maxium timeout of [" + config.getTimeoutNs + "] ns")
|
||||
}
|
||||
result
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Java API: coordinated atomic method that accepts an [[akka.stm.Atomic]].
|
||||
* Delimits the coordinated transaction. The transaction will wait for all other transactions
|
||||
* in this coordination before committing. The timeout is specified by the transaction factory.
|
||||
*
|
||||
* @throws ActorTimeoutException if the coordinated transaction times out
|
||||
*/
|
||||
def atomic[T](jatomic: Atomic[T]): T = atomic(jatomic.factory)(jatomic.atomically)
|
||||
|
||||
/**
|
||||
* Java API: coordinated atomic method that accepts an [[akka.transactor.Atomically]].
|
||||
* Delimits the coordinated transaction. The transaction will wait for all other transactions
|
||||
* in this coordination before committing. The timeout is specified by the transaction factory.
|
||||
*
|
||||
* @throws ActorTimeoutException if the coordinated transaction times out.
|
||||
*/
|
||||
def atomic(atomically: Atomically): Unit = atomic(atomically.factory)(atomically.atomically)
|
||||
|
||||
/**
|
||||
* An empty coordinated atomic block. Can be used to complete the number of parties involved
|
||||
* and wait for all transactions to complete. The default timeout is used.
|
||||
*/
|
||||
def await() = atomic(Coordinated.DefaultFactory) {}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
public class Address {
|
||||
private String location;
|
||||
|
||||
public Address(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "Address(" + location + ")";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
public class Branch {
|
||||
public Ref<Integer> left;
|
||||
public Ref<Integer> right;
|
||||
public int amount;
|
||||
|
||||
public Branch(Ref<Integer> left, Ref<Integer> right, int amount) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
import akka.stm.*;
|
||||
import static akka.stm.StmUtils.retry;
|
||||
import akka.actor.*;
|
||||
import akka.util.FiniteDuration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Brancher extends UntypedActor {
|
||||
TransactionFactory txFactory = new TransactionFactoryBuilder()
|
||||
.setBlockingAllowed(true)
|
||||
.setTrackReads(true)
|
||||
.setTimeout(new FiniteDuration(60, TimeUnit.SECONDS))
|
||||
.build();
|
||||
|
||||
public void onReceive(Object message) throws Exception {
|
||||
if (message instanceof Branch) {
|
||||
Branch branch = (Branch) message;
|
||||
final Ref<Integer> left = branch.left;
|
||||
final Ref<Integer> right = branch.right;
|
||||
final double amount = branch.amount;
|
||||
new Atomic<Integer>(txFactory) {
|
||||
public Integer atomically() {
|
||||
return new EitherOrElse<Integer>() {
|
||||
public Integer either() {
|
||||
if (left.get() < amount) {
|
||||
System.out.println("not enough on left - retrying");
|
||||
retry();
|
||||
}
|
||||
System.out.println("going left");
|
||||
return left.get();
|
||||
}
|
||||
public Integer orElse() {
|
||||
if (right.get() < amount) {
|
||||
System.out.println("not enough on right - retrying");
|
||||
retry();
|
||||
}
|
||||
System.out.println("going right");
|
||||
return right.get();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
public class CounterExample {
|
||||
final static Ref<Integer> ref = new Ref<Integer>(0);
|
||||
|
||||
public static int counter() {
|
||||
return new Atomic<Integer>() {
|
||||
public Integer atomically() {
|
||||
int inc = ref.get() + 1;
|
||||
ref.set(inc);
|
||||
return inc;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println();
|
||||
System.out.println("Counter example");
|
||||
System.out.println();
|
||||
System.out.println("counter 1: " + counter());
|
||||
System.out.println("counter 2: " + counter());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
import akka.stm.*;
|
||||
import akka.actor.*;
|
||||
|
||||
public class EitherOrElseExample {
|
||||
public static void main(String[] args) {
|
||||
System.out.println();
|
||||
System.out.println("EitherOrElse example");
|
||||
System.out.println();
|
||||
|
||||
ActorSystem application = ActorSystem.create("UntypedTransactorExample");
|
||||
|
||||
final Ref<Integer> left = new Ref<Integer>(100);
|
||||
final Ref<Integer> right = new Ref<Integer>(100);
|
||||
|
||||
ActorRef brancher = application.actorOf(new Props().withCreator(Brancher.class));
|
||||
|
||||
brancher.tell(new Branch(left, right, 500));
|
||||
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
return right.set(right.get() + 1000);
|
||||
}
|
||||
}.execute();
|
||||
|
||||
application.stop(brancher);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
public class RefExample {
|
||||
public static void main(String[] args) {
|
||||
System.out.println();
|
||||
System.out.println("Ref example");
|
||||
System.out.println();
|
||||
|
||||
final Ref<Integer> ref = new Ref<Integer>(0);
|
||||
|
||||
Integer value1 = new Atomic<Integer>() {
|
||||
public Integer atomically() {
|
||||
return ref.get();
|
||||
}
|
||||
}.execute();
|
||||
|
||||
System.out.println("value 1: " + value1);
|
||||
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
return ref.set(5);
|
||||
}
|
||||
}.execute();
|
||||
|
||||
Integer value2 = new Atomic<Integer>() {
|
||||
public Integer atomically() {
|
||||
return ref.get();
|
||||
}
|
||||
}.execute();
|
||||
|
||||
System.out.println("value 2: " + value2);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stm.*;
|
||||
import akka.actor.*;
|
||||
import akka.testkit.AkkaSpec;
|
||||
|
||||
public class RetryExample {
|
||||
public static void main(String[] args) {
|
||||
|
||||
ActorSystem application = ActorSystem.create("RetryExample", AkkaSpec.testConf());
|
||||
|
||||
final Ref<Double> account1 = new Ref<Double>(100.0);
|
||||
final Ref<Double> account2 = new Ref<Double>(100.0);
|
||||
|
||||
ActorRef transferer = application.actorOf(new Props().withCreator(Transferer.class));
|
||||
|
||||
transferer.tell(new Transfer(account1, account2, 500.0));
|
||||
// Transferer: not enough money - retrying
|
||||
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
return account1.set(account1.get() + 2000);
|
||||
}
|
||||
}.execute();
|
||||
// Transferer: transferring
|
||||
|
||||
Double acc1 = new Atomic<Double>() {
|
||||
public Double atomically() {
|
||||
return account1.get();
|
||||
}
|
||||
}.execute();
|
||||
|
||||
Double acc2 = new Atomic<Double>() {
|
||||
public Double atomically() {
|
||||
return account2.get();
|
||||
}
|
||||
}.execute();
|
||||
|
||||
System.out.println("Account 1: " + acc1);
|
||||
// Account 1: 1600.0
|
||||
|
||||
System.out.println("Account 2: " + acc2);
|
||||
// Account 2: 600.0
|
||||
|
||||
application.stop(transferer);
|
||||
|
||||
application.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
public class StmExamples {
|
||||
public static void main(String[] args) {
|
||||
System.out.println();
|
||||
System.out.println("STM examples");
|
||||
System.out.println();
|
||||
|
||||
CounterExample.main(args);
|
||||
RefExample.main(args);
|
||||
TransactionFactoryExample.main(args);
|
||||
TransactionalMapExample.main(args);
|
||||
TransactionalVectorExample.main(args);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
import org.multiverse.api.ThreadLocalTransaction;
|
||||
import org.multiverse.api.TransactionConfiguration;
|
||||
|
||||
public class TransactionFactoryExample {
|
||||
public static void main(String[] args) {
|
||||
System.out.println();
|
||||
System.out.println("TransactionFactory example");
|
||||
System.out.println();
|
||||
|
||||
TransactionFactory txFactory = new TransactionFactoryBuilder()
|
||||
.setFamilyName("example")
|
||||
.setReadonly(true)
|
||||
.build();
|
||||
|
||||
new Atomic(txFactory) {
|
||||
public Object atomically() {
|
||||
// check config has been passed to multiverse
|
||||
TransactionConfiguration config = ThreadLocalTransaction.getThreadLocalTransaction().getConfiguration();
|
||||
System.out.println("family name: " + config.getFamilyName());
|
||||
System.out.println("readonly: " + config.isReadonly());
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
public class TransactionalMapExample {
|
||||
public static void main(String[] args) {
|
||||
System.out.println();
|
||||
System.out.println("TransactionalMap example");
|
||||
System.out.println();
|
||||
|
||||
final TransactionalMap<String, User> users = new TransactionalMap<String, User>();
|
||||
|
||||
// fill users map (in a transaction)
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
users.put("bill", new User("bill"));
|
||||
users.put("mary", new User("mary"));
|
||||
users.put("john", new User("john"));
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
System.out.println("users: " + users);
|
||||
|
||||
// access users map (in a transaction)
|
||||
User user = new Atomic<User>() {
|
||||
public User atomically() {
|
||||
return users.get("bill").get();
|
||||
}
|
||||
}.execute();
|
||||
|
||||
System.out.println("user: " + user);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
public class TransactionalVectorExample {
|
||||
public static void main(String[] args) {
|
||||
System.out.println();
|
||||
System.out.println("TransactionalVector example");
|
||||
System.out.println();
|
||||
|
||||
final TransactionalVector<Address> addresses = new TransactionalVector<Address>();
|
||||
|
||||
// fill addresses vector (in a transaction)
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
addresses.add(new Address("somewhere"));
|
||||
addresses.add(new Address("somewhere else"));
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
|
||||
System.out.println("addresses: " + addresses);
|
||||
|
||||
// access addresses vector (in a transaction)
|
||||
Address address = new Atomic<Address>() {
|
||||
public Address atomically() {
|
||||
return addresses.get(0);
|
||||
}
|
||||
}.execute();
|
||||
|
||||
System.out.println("address: " + address);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
import akka.stm.*;
|
||||
|
||||
public class Transfer {
|
||||
public Ref<Double> from;
|
||||
public Ref<Double> to;
|
||||
public double amount;
|
||||
|
||||
public Transfer(Ref<Double> from, Ref<Double> to, double amount) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
import akka.stm.*;
|
||||
import static akka.stm.StmUtils.retry;
|
||||
import akka.actor.*;
|
||||
import akka.util.FiniteDuration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Transferer extends UntypedActor {
|
||||
TransactionFactory txFactory = new TransactionFactoryBuilder()
|
||||
.setBlockingAllowed(true)
|
||||
.setTrackReads(true)
|
||||
.setTimeout(new FiniteDuration(60, TimeUnit.SECONDS))
|
||||
.build();
|
||||
|
||||
public void onReceive(Object message) throws Exception {
|
||||
if (message instanceof Transfer) {
|
||||
Transfer transfer = (Transfer) message;
|
||||
final Ref<Double> from = transfer.from;
|
||||
final Ref<Double> to = transfer.to;
|
||||
final double amount = transfer.amount;
|
||||
new Atomic(txFactory) {
|
||||
public Object atomically() {
|
||||
if (from.get() < amount) {
|
||||
System.out.println("Transferer: not enough money - retrying");
|
||||
retry();
|
||||
}
|
||||
System.out.println("Transferer: transferring");
|
||||
from.set(from.get() - amount);
|
||||
to.set(to.get() + amount);
|
||||
return null;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package akka.stm.example;
|
||||
|
||||
public class User {
|
||||
private String name;
|
||||
|
||||
public User(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "User(" + name + ")";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
package akka.stm.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
import org.junit.Before;
|
||||
|
||||
import akka.stm.*;
|
||||
import static akka.stm.StmUtils.deferred;
|
||||
import static akka.stm.StmUtils.compensating;
|
||||
|
||||
import org.multiverse.api.ThreadLocalTransaction;
|
||||
import org.multiverse.api.TransactionConfiguration;
|
||||
import org.multiverse.api.exceptions.ReadonlyException;
|
||||
|
||||
|
||||
public class JavaStmTests {
|
||||
|
||||
private Ref<Integer> ref;
|
||||
|
||||
private int getRefValue() {
|
||||
return new Atomic<Integer>() {
|
||||
public Integer atomically() {
|
||||
return ref.get();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public int increment() {
|
||||
return new Atomic<Integer>() {
|
||||
public Integer atomically() {
|
||||
int inc = ref.get() + 1;
|
||||
ref.set(inc);
|
||||
return inc;
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
@Before public void initialise() {
|
||||
ref = new Ref<Integer>(0);
|
||||
}
|
||||
|
||||
@Test public void incrementRef() {
|
||||
assertEquals(0, getRefValue());
|
||||
increment();
|
||||
increment();
|
||||
increment();
|
||||
assertEquals(3, getRefValue());
|
||||
}
|
||||
|
||||
@Test public void failSetRef() {
|
||||
assertEquals(0, getRefValue());
|
||||
try {
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
ref.set(3);
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}.execute();
|
||||
} catch(RuntimeException e) {}
|
||||
assertEquals(0, getRefValue());
|
||||
}
|
||||
|
||||
@Test public void configureTransaction() {
|
||||
TransactionFactory txFactory = new TransactionFactoryBuilder()
|
||||
.setFamilyName("example")
|
||||
.setReadonly(true)
|
||||
.build();
|
||||
|
||||
// get transaction config from multiverse
|
||||
TransactionConfiguration config = new Atomic<TransactionConfiguration>(txFactory) {
|
||||
public TransactionConfiguration atomically() {
|
||||
ref.get();
|
||||
return ThreadLocalTransaction.getThreadLocalTransaction().getConfiguration();
|
||||
}
|
||||
}.execute();
|
||||
|
||||
assertEquals("example", config.getFamilyName());
|
||||
assertEquals(true, config.isReadonly());
|
||||
}
|
||||
|
||||
@Test(expected=ReadonlyException.class) public void failReadonlyTransaction() {
|
||||
TransactionFactory txFactory = new TransactionFactoryBuilder()
|
||||
.setFamilyName("example")
|
||||
.setReadonly(true)
|
||||
.build();
|
||||
|
||||
new Atomic(txFactory) {
|
||||
public Object atomically() {
|
||||
return ref.set(3);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
@Test public void deferredTask() {
|
||||
final Ref<Boolean> enteredDeferred = new Ref<Boolean>(false);
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
deferred(new Runnable() {
|
||||
public void run() {
|
||||
enteredDeferred.set(true);
|
||||
}
|
||||
});
|
||||
return ref.set(3);
|
||||
}
|
||||
}.execute();
|
||||
assertEquals(true, enteredDeferred.get());
|
||||
}
|
||||
|
||||
@Test public void compensatingTask() {
|
||||
final Ref<Boolean> enteredCompensating = new Ref<Boolean>(false);
|
||||
try {
|
||||
new Atomic() {
|
||||
public Object atomically() {
|
||||
compensating(new Runnable() {
|
||||
public void run() {
|
||||
enteredCompensating.set(true);
|
||||
}
|
||||
});
|
||||
ref.set(3);
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}.execute();
|
||||
} catch(RuntimeException e) {}
|
||||
assertEquals(true, enteredCompensating.get());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package akka.transactor.example;
|
||||
|
||||
import akka.transactor.Coordinated;
|
||||
import akka.transactor.Atomically;
|
||||
import akka.actor.Actors;
|
||||
import akka.actor.UntypedActor;
|
||||
import akka.stm.Ref;
|
||||
|
||||
public class UntypedCoordinatedCounter extends UntypedActor {
|
||||
private Ref<Integer> count = new Ref<Integer>(0);
|
||||
|
||||
private void increment() {
|
||||
//System.out.println("incrementing");
|
||||
count.set(count.get() + 1);
|
||||
}
|
||||
|
||||
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;
|
||||
if (increment.hasFriend()) {
|
||||
increment.getFriend().tell(coordinated.coordinate(new Increment()));
|
||||
}
|
||||
coordinated.atomic(new Atomically() {
|
||||
public void atomically() {
|
||||
increment();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (incoming instanceof String) {
|
||||
String message = (String) incoming;
|
||||
if (message.equals("GetCount")) {
|
||||
getSender().tell(count.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package akka.transactor.example;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Props;
|
||||
import akka.dispatch.Await;
|
||||
import akka.dispatch.Future;
|
||||
import akka.testkit.AkkaSpec;
|
||||
import akka.transactor.Coordinated;
|
||||
|
||||
import akka.util.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class UntypedCoordinatedExample {
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
|
||||
ActorSystem app = ActorSystem.create("UntypedCoordinatedExample", AkkaSpec.testConf());
|
||||
|
||||
ActorRef counter1 = app.actorOf(new Props().withCreator(UntypedCoordinatedCounter.class));
|
||||
ActorRef counter2 = app.actorOf(new Props().withCreator(UntypedCoordinatedCounter.class));
|
||||
|
||||
counter1.tell(new Coordinated(new Increment(counter2)));
|
||||
|
||||
Thread.sleep(3000);
|
||||
|
||||
long timeout = 5000;
|
||||
Duration d = Duration.create(timeout, TimeUnit.MILLISECONDS);
|
||||
|
||||
Future<Object> future1 = counter1.ask("GetCount", timeout);
|
||||
Future<Object> future2 = counter2.ask("GetCount", timeout);
|
||||
|
||||
int count1 = (Integer) Await.result(future1, d);
|
||||
System.out.println("counter 1: " + count1);
|
||||
int count2 = (Integer) Await.result(future2, d);
|
||||
System.out.println("counter 1: " + count2);
|
||||
|
||||
app.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
package akka.transactor.example;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Props;
|
||||
import akka.dispatch.Await;
|
||||
import akka.dispatch.Future;
|
||||
import akka.testkit.AkkaSpec;
|
||||
import akka.util.Duration;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class UntypedTransactorExample {
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
|
||||
ActorSystem app = ActorSystem.create("UntypedTransactorExample", AkkaSpec.testConf());
|
||||
|
||||
ActorRef counter1 = app.actorOf(new Props().withCreator(UntypedCounter.class));
|
||||
ActorRef counter2 = app.actorOf(new Props().withCreator(UntypedCounter.class));
|
||||
|
||||
counter1.tell(new Increment(counter2));
|
||||
|
||||
Thread.sleep(3000);
|
||||
|
||||
long timeout = 5000;
|
||||
Duration d = Duration.create(timeout, TimeUnit.MILLISECONDS);
|
||||
|
||||
Future<Object> future1 = counter1.ask("GetCount", timeout);
|
||||
Future<Object> future2 = counter2.ask("GetCount", timeout);
|
||||
|
||||
int count1 = (Integer) Await.result(future1, d);
|
||||
System.out.println("counter 1: " + count1);
|
||||
int count2 = (Integer) Await.result(future2, d);
|
||||
System.out.println("counter 1: " + count2);
|
||||
|
||||
app.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
package akka.transactor.test;
|
||||
|
||||
import akka.transactor.UntypedTransactor;
|
||||
|
||||
public class UntypedFailer extends UntypedTransactor {
|
||||
public void atomically(Object message) throws Exception {
|
||||
throw new ExpectedFailureException();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.stm.test
|
||||
|
||||
import org.junit.runner.RunWith
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.actor.ActorSystem
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import akka.testkit.AkkaSpec
|
||||
|
||||
@RunWith(classOf[JUnitRunner])
|
||||
class ConfigSpec extends AkkaSpec(ConfigFactory.defaultReference) {
|
||||
|
||||
"The default configuration file (i.e. reference.conf)" should {
|
||||
"contain all configuration properties for akka-stm that are used in code with their correct defaults" in {
|
||||
val config = system.settings.config
|
||||
import config._
|
||||
|
||||
// TODO are these config values used anywhere?
|
||||
|
||||
getBoolean("akka.stm.blocking-allowed") must equal(false)
|
||||
getBoolean("akka.stm.fair") must equal(true)
|
||||
getBoolean("akka.stm.interruptible") must equal(false)
|
||||
getInt("akka.stm.max-retries") must equal(1000)
|
||||
getString("akka.stm.propagation") must equal("requires")
|
||||
getBoolean("akka.stm.quick-release") must equal(true)
|
||||
getBoolean("akka.stm.speculative") must equal(true)
|
||||
getMilliseconds("akka.stm.timeout") must equal(5 * 1000)
|
||||
getString("akka.stm.trace-level") must equal("none")
|
||||
getBoolean("akka.stm.write-skew") must equal(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package akka.stm.test
|
||||
|
||||
import org.scalatest.junit.JUnitWrapperSuite
|
||||
|
||||
class JavaStmSpec extends JUnitWrapperSuite("akka.stm.test.JavaStmTests", Thread.currentThread.getContextClassLoader)
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
package akka.stm.test
|
||||
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
|
||||
class RefSpec extends WordSpec with MustMatchers {
|
||||
|
||||
import akka.stm._
|
||||
|
||||
"A Ref" should {
|
||||
|
||||
"optionally accept an initial value" in {
|
||||
val emptyRef = Ref[Int]
|
||||
val empty = atomic { emptyRef.opt }
|
||||
|
||||
empty must be(None)
|
||||
|
||||
val ref = Ref(3)
|
||||
val value = atomic { ref.get }
|
||||
|
||||
value must be(3)
|
||||
}
|
||||
|
||||
"keep the initial value, even if the first transaction is rolled back" in {
|
||||
val ref = Ref(3)
|
||||
|
||||
try {
|
||||
atomic(DefaultTransactionFactory) {
|
||||
ref.swap(5)
|
||||
throw new Exception
|
||||
}
|
||||
} catch {
|
||||
case e ⇒ {}
|
||||
}
|
||||
|
||||
val value = atomic { ref.get }
|
||||
|
||||
value must be(3)
|
||||
}
|
||||
|
||||
"be settable using set" in {
|
||||
val ref = Ref[Int]
|
||||
|
||||
atomic { ref.set(3) }
|
||||
|
||||
val value = atomic { ref.get }
|
||||
|
||||
value must be(3)
|
||||
}
|
||||
|
||||
"be settable using swap" in {
|
||||
val ref = Ref[Int]
|
||||
|
||||
atomic { ref.swap(3) }
|
||||
|
||||
val value = atomic { ref.get }
|
||||
|
||||
value must be(3)
|
||||
}
|
||||
|
||||
"be changeable using alter" in {
|
||||
val ref = Ref(0)
|
||||
|
||||
def increment = atomic {
|
||||
ref alter (_ + 1)
|
||||
}
|
||||
|
||||
increment
|
||||
increment
|
||||
increment
|
||||
|
||||
val value = atomic { ref.get }
|
||||
|
||||
value must be(3)
|
||||
}
|
||||
|
||||
"be able to be mapped" in {
|
||||
val ref1 = Ref(1)
|
||||
|
||||
val ref2 = atomic {
|
||||
ref1 map (_ + 1)
|
||||
}
|
||||
|
||||
val value1 = atomic { ref1.get }
|
||||
val value2 = atomic { ref2.get }
|
||||
|
||||
value1 must be(1)
|
||||
value2 must be(2)
|
||||
}
|
||||
|
||||
"be able to be used in a 'foreach' for comprehension" in {
|
||||
val ref = Ref(3)
|
||||
|
||||
var result = 0
|
||||
|
||||
atomic {
|
||||
for (value ← ref) {
|
||||
result += value
|
||||
}
|
||||
}
|
||||
|
||||
result must be(3)
|
||||
}
|
||||
|
||||
"be able to be used in a 'map' for comprehension" in {
|
||||
val ref1 = Ref(1)
|
||||
|
||||
val ref2 = atomic {
|
||||
for (value ← ref1) yield value + 2
|
||||
}
|
||||
|
||||
val value2 = atomic { ref2.get }
|
||||
|
||||
value2 must be(3)
|
||||
}
|
||||
|
||||
"be able to be used in a 'flatMap' for comprehension" in {
|
||||
val ref1 = Ref(1)
|
||||
val ref2 = Ref(2)
|
||||
|
||||
val ref3 = atomic {
|
||||
for {
|
||||
value1 ← ref1
|
||||
value2 ← ref2
|
||||
} yield value1 + value2
|
||||
}
|
||||
|
||||
val value3 = atomic { ref3.get }
|
||||
|
||||
value3 must be(3)
|
||||
}
|
||||
|
||||
"be able to be used in a 'filter' for comprehension" in {
|
||||
val ref1 = Ref(1)
|
||||
|
||||
val refLess2 = atomic {
|
||||
for (value ← ref1 if value < 2) yield value
|
||||
}
|
||||
|
||||
val optLess2 = atomic { refLess2.opt }
|
||||
|
||||
val refGreater2 = atomic {
|
||||
for (value ← ref1 if value > 2) yield value
|
||||
}
|
||||
|
||||
val optGreater2 = atomic { refGreater2.opt }
|
||||
|
||||
optLess2 must be(Some(1))
|
||||
optGreater2 must be(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
package akka.stm.test
|
||||
|
||||
import akka.actor.Actor
|
||||
import akka.actor.Actor._
|
||||
|
||||
import org.multiverse.api.exceptions.ReadonlyException
|
||||
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
|
||||
class StmSpec extends WordSpec with MustMatchers {
|
||||
|
||||
import akka.stm._
|
||||
|
||||
"Local STM" should {
|
||||
|
||||
"be able to do multiple consecutive atomic {..} statements" in {
|
||||
val ref = Ref(0)
|
||||
|
||||
def increment = atomic {
|
||||
ref alter (_ + 1)
|
||||
}
|
||||
|
||||
def total: Int = atomic {
|
||||
ref.getOrElse(0)
|
||||
}
|
||||
|
||||
increment
|
||||
increment
|
||||
increment
|
||||
|
||||
total must be(3)
|
||||
}
|
||||
|
||||
"be able to do nested atomic {..} statements" in {
|
||||
val ref = Ref(0)
|
||||
|
||||
def increment = atomic {
|
||||
ref alter (_ + 1)
|
||||
}
|
||||
|
||||
def total: Int = atomic {
|
||||
ref.getOrElse(0)
|
||||
}
|
||||
|
||||
atomic {
|
||||
increment
|
||||
increment
|
||||
}
|
||||
|
||||
atomic {
|
||||
increment
|
||||
total must be(3)
|
||||
}
|
||||
}
|
||||
|
||||
"roll back failing nested atomic {..} statements" in {
|
||||
val ref = Ref(0)
|
||||
|
||||
def increment = atomic {
|
||||
ref alter (_ + 1)
|
||||
}
|
||||
|
||||
def total: Int = atomic {
|
||||
ref.getOrElse(0)
|
||||
}
|
||||
|
||||
try {
|
||||
atomic(DefaultTransactionFactory) {
|
||||
increment
|
||||
increment
|
||||
throw new Exception
|
||||
}
|
||||
} catch {
|
||||
case e ⇒ {}
|
||||
}
|
||||
|
||||
total must be(0)
|
||||
}
|
||||
|
||||
"use the outer transaction settings by default" in {
|
||||
val readonlyFactory = TransactionFactory(readonly = true)
|
||||
val writableFactory = TransactionFactory(readonly = false)
|
||||
|
||||
val ref = Ref(0)
|
||||
|
||||
def writableOuter =
|
||||
atomic(writableFactory) {
|
||||
atomic(readonlyFactory) {
|
||||
ref alter (_ + 1)
|
||||
}
|
||||
}
|
||||
|
||||
def readonlyOuter =
|
||||
atomic(readonlyFactory) {
|
||||
atomic(writableFactory) {
|
||||
ref alter (_ + 1)
|
||||
}
|
||||
}
|
||||
|
||||
writableOuter must be(1)
|
||||
evaluating { readonlyOuter } must produce[ReadonlyException]
|
||||
}
|
||||
|
||||
"allow propagation settings for nested transactions" in {
|
||||
val readonlyFactory = TransactionFactory(readonly = true)
|
||||
val writableRequiresNewFactory = TransactionFactory(readonly = false, propagation = Propagation.RequiresNew)
|
||||
|
||||
val ref = Ref(0)
|
||||
|
||||
def writableOuter =
|
||||
atomic(writableRequiresNewFactory) {
|
||||
atomic(readonlyFactory) {
|
||||
ref alter (_ + 1)
|
||||
}
|
||||
}
|
||||
|
||||
def readonlyOuter =
|
||||
atomic(readonlyFactory) {
|
||||
atomic(writableRequiresNewFactory) {
|
||||
ref alter (_ + 1)
|
||||
}
|
||||
}
|
||||
|
||||
writableOuter must be(1)
|
||||
readonlyOuter must be(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor
|
||||
|
||||
import scala.concurrent.stm._
|
||||
|
||||
/**
|
||||
* Java API.
|
||||
*
|
||||
* For creating Java-friendly coordinated atomic blocks.
|
||||
*
|
||||
* @see [[akka.transactor.Coordinated]]
|
||||
*/
|
||||
trait Atomically {
|
||||
def atomically(txn: InTxn): Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* Java API.
|
||||
*
|
||||
* For creating completion handlers.
|
||||
*/
|
||||
trait CompletionHandler {
|
||||
def handle(status: Txn.Status): Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* Java API.
|
||||
*
|
||||
* To ease some of the pain of using Scala STM from Java until
|
||||
* the proper Java API is created.
|
||||
*/
|
||||
object Stm {
|
||||
/**
|
||||
* Create an STM Ref with an initial value.
|
||||
*/
|
||||
def ref[A](initialValue: A): Ref[A] = Ref(initialValue)
|
||||
|
||||
/**
|
||||
* Add a CompletionHandler to run after the current transaction
|
||||
* has committed.
|
||||
*/
|
||||
def afterCommit(handler: CompletionHandler): Unit = {
|
||||
val txn = Txn.findCurrent
|
||||
if (txn.isDefined) Txn.afterCommit(status ⇒ handler.handle(status))(txn.get)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a CompletionHandler to run after the current transaction
|
||||
* has rolled back.
|
||||
*/
|
||||
def afterRollback(handler: CompletionHandler): Unit = {
|
||||
val txn = Txn.findCurrent
|
||||
if (txn.isDefined) Txn.afterRollback(status ⇒ handler.handle(status))(txn.get)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a CompletionHandler to run after the current transaction
|
||||
* has committed or rolled back.
|
||||
*/
|
||||
def afterCompletion(handler: CompletionHandler): Unit = {
|
||||
val txn = Txn.findCurrent
|
||||
if (txn.isDefined) Txn.afterCompletion(status ⇒ handler.handle(status))(txn.get)
|
||||
}
|
||||
}
|
||||
157
akka-transactor/src/main/scala/akka/transactor/Coordinated.scala
Normal file
157
akka-transactor/src/main/scala/akka/transactor/Coordinated.scala
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor
|
||||
|
||||
import akka.AkkaException
|
||||
import akka.util.Timeout
|
||||
import scala.concurrent.stm._
|
||||
|
||||
/**
|
||||
* Akka-specific exception for coordinated transactions.
|
||||
*/
|
||||
class CoordinatedTransactionException(message: String, cause: Throwable = null) extends AkkaException(message, cause) {
|
||||
def this(msg: String) = this(msg, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinated transactions across actors.
|
||||
*/
|
||||
object Coordinated {
|
||||
def apply(message: Any = null)(implicit timeout: Timeout) = new Coordinated(message, createInitialMember(timeout))
|
||||
|
||||
def unapply(c: Coordinated): Option[Any] = Some(c.message)
|
||||
|
||||
def createInitialMember(timeout: Timeout) = CommitBarrier(timeout.duration.toMillis).addMember()
|
||||
}
|
||||
|
||||
/**
|
||||
* `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.actor.Transactor]] for an actor that implements coordinated transactions
|
||||
*/
|
||||
class Coordinated(val message: Any, member: CommitBarrier.Member) {
|
||||
|
||||
// Java API constructors
|
||||
|
||||
def this(message: Any, timeout: Timeout) = this(message, Coordinated.createInitialMember(timeout))
|
||||
|
||||
def this(timeout: Timeout) = this(null, Coordinated.createInitialMember(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) = {
|
||||
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) = new Coordinated(msg, member)
|
||||
|
||||
/**
|
||||
* Java API: get the message for this Coordinated.
|
||||
*/
|
||||
def getMessage() = 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) = 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[T](body: InTxn ⇒ T): T = {
|
||||
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 an [[akka.transactor.Atomically]].
|
||||
* 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(atomically: Atomically): Unit = atomic(txn ⇒ atomically.atomically(txn))
|
||||
|
||||
/**
|
||||
* An empty coordinated atomic block. Can be used to complete the number of members involved
|
||||
* and wait for all transactions to complete.
|
||||
*/
|
||||
def await() = atomic(txn ⇒ ())
|
||||
|
||||
/**
|
||||
* Cancel this Coordinated transaction.
|
||||
*/
|
||||
def cancel(info: Any) = member.cancel(CommitBarrier.UserCancel(info))
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
package akka.transactor
|
||||
|
||||
import akka.actor.{ Actor, ActorRef }
|
||||
import akka.stm.{ DefaultTransactionConfig, TransactionFactory }
|
||||
import scala.concurrent.stm.InTxn
|
||||
|
||||
/**
|
||||
* Used for specifying actor refs and messages to send to during coordination.
|
||||
|
|
@ -15,10 +15,9 @@ 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.stm.Coordinated]] where
|
||||
* first any coordination messages are sent to other transactors, then the coordinated
|
||||
* transaction is entered.
|
||||
* Transactors can also accept explicitly sent `Coordinated` messages.
|
||||
* 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
|
||||
|
|
@ -30,16 +29,16 @@ case class SendTo(actor: ActorRef, message: Option[Any] = None)
|
|||
* class Counter extends Transactor {
|
||||
* val count = Ref(0)
|
||||
*
|
||||
* def atomically = {
|
||||
* case Increment => count alter (_ + 1)
|
||||
* 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.actor.Transactor.SendTo]] objects, pairs of `ActorRef` and a message.
|
||||
* The `coordinate` method maps a message to a set of [[akka.actor.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.
|
||||
|
|
@ -54,8 +53,8 @@ case class SendTo(actor: ActorRef, message: Option[Any] = None)
|
|||
* case Increment => include(friend)
|
||||
* }
|
||||
*
|
||||
* def atomically = {
|
||||
* case Increment => count alter (_ + 1)
|
||||
* def atomically = implicit txn => {
|
||||
* case Increment => count transform (_ + 1)
|
||||
* }
|
||||
* }
|
||||
* }}}
|
||||
|
|
@ -91,16 +90,9 @@ case class SendTo(actor: ActorRef, message: Option[Any] = None)
|
|||
* can implement normal actor behavior, or use the normal STM atomic for
|
||||
* local transactions.
|
||||
*
|
||||
* @see [[akka.stm.Coordinated]] for more information about the underlying mechanism
|
||||
* @see [[akka.transactor.Coordinated]] for more information about the underlying mechanism
|
||||
*/
|
||||
trait Transactor extends Actor {
|
||||
private lazy val txFactory = transactionFactory
|
||||
|
||||
/**
|
||||
* Create default transaction factory. Override to provide custom configuration.
|
||||
*/
|
||||
def transactionFactory = TransactionFactory(DefaultTransactionConfig)
|
||||
|
||||
/**
|
||||
* Implement a general pattern for using coordinated transactions.
|
||||
*/
|
||||
|
|
@ -111,12 +103,12 @@ trait Transactor extends Actor {
|
|||
sendTo.actor ! coordinated(sendTo.message.getOrElse(message))
|
||||
}
|
||||
(before orElse doNothing)(message)
|
||||
coordinated.atomic(txFactory) { (atomically 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))
|
||||
else receive(Coordinated(message)(context.system.settings.ActorTimeout))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,8 +150,16 @@ trait Transactor extends Actor {
|
|||
|
||||
/**
|
||||
* 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: Receive
|
||||
def atomically: InTxn ⇒ Receive
|
||||
|
||||
/**
|
||||
* A Receive block that runs after the coordinated transaction.
|
||||
|
|
@ -5,23 +5,14 @@
|
|||
package akka.transactor
|
||||
|
||||
import akka.actor.{ UntypedActor, ActorRef }
|
||||
import akka.stm.{ DefaultTransactionConfig, TransactionFactory }
|
||||
|
||||
import java.util.{ Set ⇒ JSet }
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.concurrent.stm.InTxn
|
||||
import java.util.{ Set ⇒ JSet }
|
||||
|
||||
/**
|
||||
* An UntypedActor version of transactor for using from Java.
|
||||
*/
|
||||
abstract class UntypedTransactor extends UntypedActor {
|
||||
private lazy val txFactory = transactionFactory
|
||||
|
||||
/**
|
||||
* Create default transaction factory. Override to provide custom configuration.
|
||||
*/
|
||||
def transactionFactory = TransactionFactory(DefaultTransactionConfig)
|
||||
|
||||
/**
|
||||
* Implement a general pattern for using coordinated transactions.
|
||||
*/
|
||||
|
|
@ -34,12 +25,12 @@ abstract class UntypedTransactor extends UntypedActor {
|
|||
sendTo.actor.tell(coordinated(sendTo.message.getOrElse(message)))
|
||||
}
|
||||
before(message)
|
||||
coordinated.atomic(txFactory) { atomically(message) }
|
||||
coordinated.atomic { txn ⇒ atomically(txn, message) }
|
||||
after(message)
|
||||
}
|
||||
case message ⇒ {
|
||||
val normal = normally(message)
|
||||
if (!normal) onReceive(Coordinated(message))
|
||||
if (!normal) onReceive(Coordinated(message)(context.system.settings.ActorTimeout))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -93,7 +84,7 @@ abstract class UntypedTransactor extends UntypedActor {
|
|||
* The Receive block to run inside the coordinated transaction.
|
||||
*/
|
||||
@throws(classOf[Exception])
|
||||
def atomically(message: Any) {}
|
||||
def atomically(txn: InTxn, message: Any) {}
|
||||
|
||||
/**
|
||||
* A Receive block that runs after the coordinated transaction.
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
package akka.transactor.test;
|
||||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor;
|
||||
|
||||
public class ExpectedFailureException extends RuntimeException {
|
||||
public ExpectedFailureException() {
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
package akka.transactor.test;
|
||||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import java.util.List;
|
||||
|
|
@ -1,33 +1,28 @@
|
|||
package akka.transactor.test;
|
||||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor;
|
||||
|
||||
import akka.transactor.Coordinated;
|
||||
import akka.transactor.Atomically;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Actors;
|
||||
import akka.actor.UntypedActor;
|
||||
import akka.stm.*;
|
||||
import akka.util.FiniteDuration;
|
||||
|
||||
import org.multiverse.api.StmUtils;
|
||||
|
||||
import scala.concurrent.stm.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class UntypedCoordinatedCounter extends UntypedActor {
|
||||
private String name;
|
||||
private Ref<Integer> count = new Ref<Integer>(0);
|
||||
private TransactionFactory txFactory = new TransactionFactoryBuilder()
|
||||
.setTimeout(new FiniteDuration(3, TimeUnit.SECONDS))
|
||||
.build();
|
||||
private Ref<Integer> count = Stm.ref(0);
|
||||
|
||||
public UntypedCoordinatedCounter(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
private void increment() {
|
||||
//System.out.println(name + ": incrementing");
|
||||
count.set(count.get() + 1);
|
||||
private void increment(InTxn txn) {
|
||||
Integer newValue = count.get(txn) + 1;
|
||||
count.set(newValue, txn);
|
||||
}
|
||||
|
||||
public void onReceive(Object incoming) throws Exception {
|
||||
|
|
@ -38,20 +33,24 @@ public class UntypedCoordinatedCounter extends UntypedActor {
|
|||
Increment increment = (Increment) message;
|
||||
List<ActorRef> friends = increment.getFriends();
|
||||
final CountDownLatch latch = increment.getLatch();
|
||||
final CompletionHandler countDown = new CompletionHandler() {
|
||||
public void handle(Txn.Status status) {
|
||||
latch.countDown();
|
||||
}
|
||||
};
|
||||
if (!friends.isEmpty()) {
|
||||
Increment coordMessage = new Increment(friends.subList(1, friends.size()), latch);
|
||||
friends.get(0).tell(coordinated.coordinate(coordMessage));
|
||||
}
|
||||
coordinated.atomic(new Atomically(txFactory) {
|
||||
public void atomically() {
|
||||
increment();
|
||||
StmUtils.scheduleDeferredTask(new Runnable() { public void run() { latch.countDown(); } });
|
||||
StmUtils.scheduleCompensatingTask(new Runnable() { public void run() { latch.countDown(); } });
|
||||
coordinated.atomic(new Atomically() {
|
||||
public void atomically(InTxn txn) {
|
||||
increment(txn);
|
||||
Stm.afterCompletion(countDown);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if ("GetCount".equals(incoming)) {
|
||||
getSender().tell(count.get());
|
||||
getSender().tell(count.single().get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,28 @@
|
|||
package akka.transactor.test;
|
||||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import akka.dispatch.Await;
|
||||
import akka.util.Duration;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.Before;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.transactor.Coordinated;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Props;
|
||||
import akka.actor.UntypedActor;
|
||||
import akka.actor.UntypedActorFactory;
|
||||
import akka.dispatch.Await;
|
||||
import akka.dispatch.Future;
|
||||
import akka.testkit.AkkaSpec;
|
||||
import akka.testkit.EventFilter;
|
||||
import akka.testkit.ErrorFilter;
|
||||
import akka.testkit.TestEvent;
|
||||
import akka.transactor.CoordinatedTransactionException;
|
||||
import akka.util.Timeout;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -33,13 +34,11 @@ import scala.collection.JavaConverters;
|
|||
import scala.collection.Seq;
|
||||
|
||||
public class UntypedCoordinatedIncrementTest {
|
||||
ActorSystem application = ActorSystem.create("UntypedCoordinatedIncrementTest", AkkaSpec.testConf());
|
||||
|
||||
private static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeAll() {
|
||||
system = ActorSystem.create("UntypedTransactorTest", AkkaSpec.testConf());
|
||||
system = ActorSystem.create("UntypedCoordinatedIncrementTest", AkkaSpec.testConf());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
|
|
@ -52,37 +51,38 @@ public class UntypedCoordinatedIncrementTest {
|
|||
ActorRef failer;
|
||||
|
||||
int numCounters = 3;
|
||||
int timeout = 5;
|
||||
int askTimeout = 5000;
|
||||
int timeoutSeconds = 5;
|
||||
|
||||
Timeout timeout = new Timeout(timeoutSeconds, TimeUnit.SECONDS);
|
||||
|
||||
@Before
|
||||
public void initialise() {
|
||||
Props p = new Props().withCreator(UntypedFailer.class);
|
||||
counters = new ArrayList<ActorRef>();
|
||||
for (int i = 1; i <= numCounters; i++) {
|
||||
final String name = "counter" + i;
|
||||
ActorRef counter = application.actorOf(new Props().withCreator(new UntypedActorFactory() {
|
||||
ActorRef counter = system.actorOf(new Props(new UntypedActorFactory() {
|
||||
public UntypedActor create() {
|
||||
return new UntypedCoordinatedCounter(name);
|
||||
}
|
||||
}));
|
||||
counters.add(counter);
|
||||
}
|
||||
failer = application.actorOf(p);
|
||||
failer = system.actorOf(new Props(UntypedFailer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incrementAllCountersWithSuccessfulTransaction() {
|
||||
CountDownLatch incrementLatch = new CountDownLatch(numCounters);
|
||||
Increment message = new Increment(counters.subList(1, counters.size()), incrementLatch);
|
||||
counters.get(0).tell(new Coordinated(message));
|
||||
counters.get(0).tell(new Coordinated(message, timeout));
|
||||
try {
|
||||
incrementLatch.await(timeout, TimeUnit.SECONDS);
|
||||
incrementLatch.await(timeoutSeconds, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException exception) {
|
||||
}
|
||||
for (ActorRef counter : counters) {
|
||||
Future<Object> future = counter.ask("GetCount", askTimeout);
|
||||
assertEquals(1, ((Integer) Await.result(future, Duration.create(timeout, TimeUnit.SECONDS))).intValue());
|
||||
Future<Object> future = counter.ask("GetCount", timeout);
|
||||
int count = (Integer) Await.result(future, timeout.duration());
|
||||
assertEquals(1, count);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,28 +91,24 @@ public class UntypedCoordinatedIncrementTest {
|
|||
EventFilter expectedFailureFilter = (EventFilter) new ErrorFilter(ExpectedFailureException.class);
|
||||
EventFilter coordinatedFilter = (EventFilter) new ErrorFilter(CoordinatedTransactionException.class);
|
||||
Seq<EventFilter> ignoreExceptions = seq(expectedFailureFilter, coordinatedFilter);
|
||||
application.eventStream().publish(new TestEvent.Mute(ignoreExceptions));
|
||||
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));
|
||||
actors.get(0).tell(new Coordinated(message, timeout));
|
||||
try {
|
||||
incrementLatch.await(timeout, TimeUnit.SECONDS);
|
||||
incrementLatch.await(timeoutSeconds, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException exception) {
|
||||
}
|
||||
for (ActorRef counter : counters) {
|
||||
Future<Object>future = counter.ask("GetCount", askTimeout);
|
||||
assertEquals(0,((Integer) Await.result(future, Duration.create(timeout, TimeUnit.SECONDS))).intValue());
|
||||
Future<Object>future = counter.ask("GetCount", timeout);
|
||||
int count = (Integer) Await.result(future, timeout.duration());
|
||||
assertEquals(0, count);
|
||||
}
|
||||
}
|
||||
|
||||
public <A> Seq<A> seq(A... args) {
|
||||
return JavaConverters.collectionAsScalaIterableConverter(Arrays.asList(args)).asScala().toSeq();
|
||||
}
|
||||
|
||||
@After
|
||||
public void stop() {
|
||||
application.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package akka.transactor.test;
|
||||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.transactor.UntypedTransactor;
|
||||
import akka.transactor.SendTo;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.stm.*;
|
||||
import akka.util.FiniteDuration;
|
||||
|
||||
import org.multiverse.api.StmUtils;
|
||||
|
||||
import scala.concurrent.stm.*;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
|
@ -15,21 +15,15 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
public class UntypedCounter extends UntypedTransactor {
|
||||
private String name;
|
||||
private Ref<Integer> count = new Ref<Integer>(0);
|
||||
private Ref<Integer> count = Stm.ref(0);
|
||||
|
||||
public UntypedCounter(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override public TransactionFactory transactionFactory() {
|
||||
return new TransactionFactoryBuilder()
|
||||
.setTimeout(new FiniteDuration(3, TimeUnit.SECONDS))
|
||||
.build();
|
||||
}
|
||||
|
||||
private void increment() {
|
||||
//System.out.println(name + ": incrementing");
|
||||
count.set(count.get() + 1);
|
||||
private void increment(InTxn txn) {
|
||||
Integer newValue = count.get(txn) + 1;
|
||||
count.set(newValue, txn);
|
||||
}
|
||||
|
||||
@Override public Set<SendTo> coordinate(Object message) {
|
||||
|
|
@ -47,30 +41,22 @@ public class UntypedCounter extends UntypedTransactor {
|
|||
}
|
||||
}
|
||||
|
||||
@Override public void before(Object message) {
|
||||
//System.out.println(name + ": before transaction");
|
||||
}
|
||||
|
||||
public void atomically(Object message) {
|
||||
public void atomically(InTxn txn, Object message) {
|
||||
if (message instanceof Increment) {
|
||||
increment();
|
||||
increment(txn);
|
||||
final Increment increment = (Increment) message;
|
||||
StmUtils.scheduleDeferredTask(new Runnable() {
|
||||
public void run() { increment.getLatch().countDown(); }
|
||||
});
|
||||
StmUtils.scheduleCompensatingTask(new Runnable() {
|
||||
public void run() { increment.getLatch().countDown(); }
|
||||
});
|
||||
CompletionHandler countDown = new CompletionHandler() {
|
||||
public void handle(Txn.Status status) {
|
||||
increment.getLatch().countDown();
|
||||
}
|
||||
};
|
||||
Stm.afterCompletion(countDown);
|
||||
}
|
||||
|
||||
@Override public void after(Object message) {
|
||||
//System.out.println(name + ": after transaction");
|
||||
}
|
||||
|
||||
@Override public boolean normally(Object message) {
|
||||
if ("GetCount".equals(message)) {
|
||||
getSender().tell(count.get());
|
||||
getSender().tell(count.single().get());
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor;
|
||||
|
||||
import scala.concurrent.stm.InTxn;
|
||||
|
||||
public class UntypedFailer extends UntypedTransactor {
|
||||
public void atomically(InTxn txn, Object message) throws Exception {
|
||||
throw new ExpectedFailureException();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
package akka.transactor.test;
|
||||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import akka.dispatch.Await;
|
||||
import akka.util.Duration;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
|
@ -14,11 +16,13 @@ import akka.actor.ActorRef;
|
|||
import akka.actor.Props;
|
||||
import akka.actor.UntypedActor;
|
||||
import akka.actor.UntypedActorFactory;
|
||||
import akka.dispatch.Await;
|
||||
import akka.dispatch.Future;
|
||||
import akka.testkit.AkkaSpec;
|
||||
import akka.testkit.EventFilter;
|
||||
import akka.testkit.ErrorFilter;
|
||||
import akka.testkit.TestEvent;
|
||||
import akka.transactor.CoordinatedTransactionException;
|
||||
import akka.util.Timeout;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -28,7 +32,6 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import scala.collection.JavaConverters;
|
||||
import scala.collection.Seq;
|
||||
import akka.testkit.AkkaSpec;
|
||||
|
||||
public class UntypedTransactorTest {
|
||||
|
||||
|
|
@ -49,22 +52,23 @@ public class UntypedTransactorTest {
|
|||
ActorRef failer;
|
||||
|
||||
int numCounters = 3;
|
||||
int timeout = 5;
|
||||
int askTimeout = 5000;
|
||||
int timeoutSeconds = 5;
|
||||
|
||||
Timeout timeout = new Timeout(timeoutSeconds, TimeUnit.SECONDS);
|
||||
|
||||
@Before
|
||||
public void initialise() {
|
||||
counters = new ArrayList<ActorRef>();
|
||||
for (int i = 1; i <= numCounters; i++) {
|
||||
final String name = "counter" + i;
|
||||
ActorRef counter = system.actorOf(new Props().withCreator(new UntypedActorFactory() {
|
||||
ActorRef counter = system.actorOf(new Props(new UntypedActorFactory() {
|
||||
public UntypedActor create() {
|
||||
return new UntypedCounter(name);
|
||||
}
|
||||
}));
|
||||
counters.add(counter);
|
||||
}
|
||||
failer = system.actorOf(new Props().withCreator(UntypedFailer.class));
|
||||
failer = system.actorOf(new Props(UntypedFailer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -73,12 +77,12 @@ public class UntypedTransactorTest {
|
|||
Increment message = new Increment(counters.subList(1, counters.size()), incrementLatch);
|
||||
counters.get(0).tell(message);
|
||||
try {
|
||||
incrementLatch.await(timeout, TimeUnit.SECONDS);
|
||||
incrementLatch.await(timeoutSeconds, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException exception) {
|
||||
}
|
||||
for (ActorRef counter : counters) {
|
||||
Future<Object> future = counter.ask("GetCount", askTimeout);
|
||||
int count = (Integer) Await.result(future, Duration.create(askTimeout, TimeUnit.MILLISECONDS));
|
||||
Future<Object> future = counter.ask("GetCount", timeout);
|
||||
int count = (Integer) Await.result(future, timeout.duration());
|
||||
assertEquals(1, count);
|
||||
}
|
||||
}
|
||||
|
|
@ -95,12 +99,12 @@ public class UntypedTransactorTest {
|
|||
Increment message = new Increment(actors.subList(1, actors.size()), incrementLatch);
|
||||
actors.get(0).tell(message);
|
||||
try {
|
||||
incrementLatch.await(timeout, TimeUnit.SECONDS);
|
||||
incrementLatch.await(timeoutSeconds, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException exception) {
|
||||
}
|
||||
for (ActorRef counter : counters) {
|
||||
Future<Object> future = counter.ask("GetCount", askTimeout);
|
||||
int count = (Integer) Await.result(future, Duration.create(askTimeout, TimeUnit.MILLISECONDS));
|
||||
Future<Object> future = counter.ask("GetCount", timeout);
|
||||
int count = (Integer) Await.result(future, timeout.duration());
|
||||
assertEquals(0, count);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor
|
||||
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.actor._
|
||||
import akka.stm.{ Ref, TransactionFactory }
|
||||
import akka.dispatch.Await
|
||||
import akka.util.duration._
|
||||
import akka.util.Timeout
|
||||
import akka.testkit._
|
||||
import akka.dispatch.Await
|
||||
import scala.concurrent.stm._
|
||||
|
||||
object CoordinatedIncrement {
|
||||
case class Increment(friends: Seq[ActorRef])
|
||||
|
|
@ -17,34 +20,27 @@ object CoordinatedIncrement {
|
|||
class Counter(name: String) extends Actor {
|
||||
val count = Ref(0)
|
||||
|
||||
implicit val txFactory = TransactionFactory(timeout = 3 seconds)
|
||||
|
||||
def increment = {
|
||||
count alter (_ + 1)
|
||||
}
|
||||
|
||||
def receive = {
|
||||
case coordinated @ Coordinated(Increment(friends)) ⇒ {
|
||||
if (friends.nonEmpty) {
|
||||
friends.head ! coordinated(Increment(friends.tail))
|
||||
}
|
||||
coordinated atomic {
|
||||
increment
|
||||
coordinated.atomic { implicit t ⇒
|
||||
count transform (_ + 1)
|
||||
}
|
||||
}
|
||||
|
||||
case GetCount ⇒ sender ! count.get
|
||||
case GetCount ⇒ sender ! count.single.get
|
||||
}
|
||||
}
|
||||
|
||||
class ExpectedFailureException extends RuntimeException("Expected failure")
|
||||
|
||||
class Failer extends Actor {
|
||||
val txFactory = TransactionFactory(timeout = 3 seconds)
|
||||
|
||||
def receive = {
|
||||
case coordinated @ Coordinated(Increment(friends)) ⇒ {
|
||||
coordinated.atomic(txFactory) {
|
||||
coordinated.atomic { t ⇒
|
||||
throw new ExpectedFailureException
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,23 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor
|
||||
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import org.scalatest.BeforeAndAfterAll
|
||||
import akka.actor.ActorSystem
|
||||
|
||||
import akka.actor._
|
||||
import akka.util.Timeout
|
||||
import akka.stm._
|
||||
import akka.dispatch.Await
|
||||
import akka.util.duration._
|
||||
import akka.util.Timeout
|
||||
import akka.testkit._
|
||||
import akka.testkit.TestEvent.Mute
|
||||
import scala.concurrent.stm._
|
||||
import scala.util.Random.{ nextInt ⇒ random }
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import akka.testkit.TestEvent.Mute
|
||||
import akka.dispatch.Await
|
||||
|
||||
object FickleFriends {
|
||||
case class FriendlyIncrement(friends: Seq[ActorRef], latch: CountDownLatch)
|
||||
case class FriendlyIncrement(friends: Seq[ActorRef], timeout: Timeout, latch: CountDownLatch)
|
||||
case class Increment(friends: Seq[ActorRef])
|
||||
case object GetCount
|
||||
|
||||
|
|
@ -25,24 +27,22 @@ object FickleFriends {
|
|||
class Coordinator(name: String) extends Actor {
|
||||
val count = Ref(0)
|
||||
|
||||
implicit val txFactory = TransactionFactory(timeout = 3 seconds)
|
||||
|
||||
def increment = {
|
||||
count alter (_ + 1)
|
||||
def increment(implicit txn: InTxn) = {
|
||||
count transform (_ + 1)
|
||||
}
|
||||
|
||||
def receive = {
|
||||
case FriendlyIncrement(friends, latch) ⇒ {
|
||||
case FriendlyIncrement(friends, timeout, latch) ⇒ {
|
||||
var success = false
|
||||
while (!success) {
|
||||
try {
|
||||
val coordinated = Coordinated()
|
||||
val coordinated = Coordinated()(timeout)
|
||||
if (friends.nonEmpty) {
|
||||
friends.head ! coordinated(Increment(friends.tail))
|
||||
}
|
||||
coordinated atomic {
|
||||
coordinated.atomic { implicit t ⇒
|
||||
increment
|
||||
deferred {
|
||||
Txn.afterCommit { status ⇒
|
||||
success = true
|
||||
latch.countDown()
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ object FickleFriends {
|
|||
}
|
||||
}
|
||||
|
||||
case GetCount ⇒ sender ! count.get
|
||||
case GetCount ⇒ sender ! count.single.get
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,14 +65,18 @@ object FickleFriends {
|
|||
class FickleCounter(name: String) extends Actor {
|
||||
val count = Ref(0)
|
||||
|
||||
implicit val txFactory = TransactionFactory(timeout = 3 seconds)
|
||||
val maxFailures = 3
|
||||
var failures = 0
|
||||
|
||||
def increment = {
|
||||
count alter (_ + 1)
|
||||
def increment(implicit txn: InTxn) = {
|
||||
count transform (_ + 1)
|
||||
}
|
||||
|
||||
def failIf(x: Int, y: Int) = {
|
||||
if (x == y) throw new ExpectedFailureException("Random fail at position " + x)
|
||||
if (x == y && failures < maxFailures) {
|
||||
failures += 1
|
||||
throw new ExpectedFailureException("Random fail at position " + x)
|
||||
}
|
||||
}
|
||||
|
||||
def receive = {
|
||||
|
|
@ -83,14 +87,14 @@ object FickleFriends {
|
|||
friends.head ! coordinated(Increment(friends.tail))
|
||||
}
|
||||
failIf(failAt, 1)
|
||||
coordinated atomic {
|
||||
coordinated.atomic { implicit t ⇒
|
||||
failIf(failAt, 2)
|
||||
increment
|
||||
failIf(failAt, 3)
|
||||
}
|
||||
}
|
||||
|
||||
case GetCount ⇒ sender ! count.get
|
||||
case GetCount ⇒ sender ! count.single.get
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -119,7 +123,7 @@ class FickleFriendsSpec extends AkkaSpec with BeforeAndAfterAll {
|
|||
system.eventStream.publish(Mute(ignoreExceptions))
|
||||
val (counters, coordinator) = actorOfs
|
||||
val latch = new CountDownLatch(1)
|
||||
coordinator ! FriendlyIncrement(counters, latch)
|
||||
coordinator ! FriendlyIncrement(counters, timeout, latch)
|
||||
latch.await // this could take a while
|
||||
Await.result(coordinator ? GetCount, timeout.duration) must be === 1
|
||||
for (counter ← counters) {
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor
|
||||
|
||||
import org.scalatest.junit.JUnitWrapperSuite
|
||||
|
||||
class JavaUntypedCoordinatedSpec extends JUnitWrapperSuite(
|
||||
"akka.transactor.test.UntypedCoordinatedIncrementTest",
|
||||
"akka.transactor.UntypedCoordinatedIncrementTest",
|
||||
Thread.currentThread.getContextClassLoader)
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor
|
||||
|
||||
import org.scalatest.junit.JUnitWrapperSuite
|
||||
|
||||
class JavaUntypedTransactorSpec extends JUnitWrapperSuite(
|
||||
"akka.transactor.test.UntypedTransactorTest",
|
||||
"akka.transactor.UntypedTransactorTest",
|
||||
Thread.currentThread.getContextClassLoader)
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.transactor
|
||||
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.actor._
|
||||
import akka.util.Timeout
|
||||
import akka.stm._
|
||||
import akka.util.duration._
|
||||
import akka.testkit._
|
||||
import akka.dispatch.Await
|
||||
import akka.util.duration._
|
||||
import akka.util.Timeout
|
||||
import akka.testkit._
|
||||
import scala.concurrent.stm._
|
||||
|
||||
object TransactorIncrement {
|
||||
case class Increment(friends: Seq[ActorRef], latch: TestLatch)
|
||||
|
|
@ -18,10 +18,8 @@ object TransactorIncrement {
|
|||
class Counter(name: String) extends Transactor {
|
||||
val count = Ref(0)
|
||||
|
||||
override def transactionFactory = TransactionFactory(timeout = 3 seconds)
|
||||
|
||||
def increment = {
|
||||
count alter (_ + 1)
|
||||
def increment(implicit txn: InTxn) = {
|
||||
count transform (_ + 1)
|
||||
}
|
||||
|
||||
override def coordinate = {
|
||||
|
|
@ -35,11 +33,10 @@ object TransactorIncrement {
|
|||
case i: Increment ⇒
|
||||
}
|
||||
|
||||
def atomically = {
|
||||
def atomically = implicit txn ⇒ {
|
||||
case Increment(friends, latch) ⇒ {
|
||||
increment
|
||||
deferred { latch.countDown() }
|
||||
compensating { latch.countDown() }
|
||||
Txn.afterCompletion { status ⇒ latch.countDown() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,14 +45,14 @@ object TransactorIncrement {
|
|||
}
|
||||
|
||||
override def normally = {
|
||||
case GetCount ⇒ sender ! count.get
|
||||
case GetCount ⇒ sender ! count.single.get
|
||||
}
|
||||
}
|
||||
|
||||
class ExpectedFailureException extends RuntimeException("Expected failure")
|
||||
|
||||
class Failer extends Transactor {
|
||||
def atomically = {
|
||||
def atomically = implicit txn ⇒ {
|
||||
case _ ⇒ throw new ExpectedFailureException
|
||||
}
|
||||
}
|
||||
|
|
@ -65,10 +62,10 @@ object SimpleTransactor {
|
|||
case class Set(ref: Ref[Int], value: Int, latch: TestLatch)
|
||||
|
||||
class Setter extends Transactor {
|
||||
def atomically = {
|
||||
def atomically = implicit txn ⇒ {
|
||||
case Set(ref, value, latch) ⇒ {
|
||||
ref.set(value)
|
||||
deferred { latch.countDown() }
|
||||
ref() = value
|
||||
Txn.afterCompletion { status ⇒ latch.countDown() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -95,7 +92,7 @@ class TransactorSpec extends AkkaSpec {
|
|||
val (counters, failer) = createTransactors
|
||||
val incrementLatch = TestLatch(numCounters)
|
||||
counters(0) ! Increment(counters.tail, incrementLatch)
|
||||
incrementLatch.await
|
||||
Await.ready(incrementLatch, 5 seconds)
|
||||
for (counter ← counters) {
|
||||
Await.result(counter ? GetCount, timeout.duration) must be === 1
|
||||
}
|
||||
|
|
@ -112,7 +109,7 @@ class TransactorSpec extends AkkaSpec {
|
|||
val (counters, failer) = createTransactors
|
||||
val failLatch = TestLatch(numCounters)
|
||||
counters(0) ! Increment(counters.tail :+ failer, failLatch)
|
||||
failLatch.await
|
||||
Await.ready(failLatch, 5 seconds)
|
||||
for (counter ← counters) {
|
||||
Await.result(counter ? GetCount, timeout.duration) must be === 0
|
||||
}
|
||||
|
|
@ -128,8 +125,8 @@ class TransactorSpec extends AkkaSpec {
|
|||
val ref = Ref(0)
|
||||
val latch = TestLatch(1)
|
||||
transactor ! Set(ref, 5, latch)
|
||||
latch.await
|
||||
val value = atomic { ref.get }
|
||||
Await.ready(latch, 5 seconds)
|
||||
val value = ref.single.get
|
||||
value must be === 5
|
||||
system.stop(transactor)
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ object AkkaBuild extends Build {
|
|||
Unidoc.unidocExclude := Seq(samples.id, tutorials.id),
|
||||
Dist.distExclude := Seq(actorTests.id, akkaSbtPlugin.id, docs.id)
|
||||
),
|
||||
aggregate = Seq(actor, testkit, actorTests, remote, slf4j, agent, mailboxes, kernel, akkaSbtPlugin, samples, tutorials, docs)
|
||||
aggregate = Seq(actor, testkit, actorTests, remote, slf4j, agent, transactor, mailboxes, kernel, akkaSbtPlugin, samples, tutorials, docs)
|
||||
)
|
||||
|
||||
lazy val actor = Project(
|
||||
|
|
@ -103,6 +103,15 @@ object AkkaBuild extends Build {
|
|||
)
|
||||
)
|
||||
|
||||
lazy val transactor = Project(
|
||||
id = "akka-transactor",
|
||||
base = file("akka-transactor"),
|
||||
dependencies = Seq(actor, testkit % "test->test"),
|
||||
settings = defaultSettings ++ Seq(
|
||||
libraryDependencies ++= Dependencies.transactor
|
||||
)
|
||||
)
|
||||
|
||||
// lazy val amqp = Project(
|
||||
// id = "akka-amqp",
|
||||
// base = file("akka-amqp"),
|
||||
|
|
@ -265,7 +274,7 @@ object AkkaBuild extends Build {
|
|||
lazy val docs = Project(
|
||||
id = "akka-docs",
|
||||
base = file("akka-docs"),
|
||||
dependencies = Seq(actor, testkit % "test->test", remote, slf4j, agent, fileMailbox, mongoMailbox, redisMailbox, beanstalkMailbox, zookeeperMailbox),
|
||||
dependencies = Seq(actor, testkit % "test->test", remote, slf4j, agent, transactor, fileMailbox, mongoMailbox, redisMailbox, beanstalkMailbox, zookeeperMailbox),
|
||||
settings = defaultSettings ++ Seq(
|
||||
unmanagedSourceDirectories in Test <<= baseDirectory { _ ** "code" get },
|
||||
libraryDependencies ++= Dependencies.docs,
|
||||
|
|
@ -380,6 +389,8 @@ object Dependencies {
|
|||
|
||||
val agent = Seq(scalaStm, Test.scalatest, Test.junit)
|
||||
|
||||
val transactor = Seq(scalaStm, Test.scalatest, Test.junit)
|
||||
|
||||
val amqp = Seq(rabbit, commonsIo, protobuf)
|
||||
|
||||
val mailboxes = Seq(Test.scalatest, Test.junit)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue