Merge pull request #179 from jboner/migrate-transactor

Migrate transactor to scala-stm
This commit is contained in:
Peter Vlugter 2011-12-21 15:47:36 -08:00
commit 0eb1ecbbf4
71 changed files with 1345 additions and 3829 deletions

View file

@ -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.
Akkas STM implements the concept in `Clojures <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 Akkas 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

View file

@ -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);

View file

@ -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.
Akkas 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 Akkas 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.

View file

@ -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()
}

View file

@ -23,7 +23,6 @@ import akka.japi.Function;
//#import-function //#import-function
//#import-timeout //#import-timeout
import akka.util.Duration;
import akka.util.Timeout; import akka.util.Timeout;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
//#import-timeout //#import-timeout
@ -86,7 +85,7 @@ public class AgentDocTest {
//#send-off //#send-off
//#read-await //#read-await
Integer result = agent.await(new Timeout(Duration.create(5, SECONDS))); Integer result = agent.await(new Timeout(5, SECONDS));
//#read-await //#read-await
assertEquals(result, new Integer(14)); assertEquals(result, new Integer(14));

View file

@ -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

View 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
}
}
}
}

View 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

View file

@ -1,13 +1,17 @@
package akka.transactor.example; /**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
import akka.transactor.UntypedTransactor; package akka.docs.transactor;
import akka.transactor.SendTo;
import akka.stm.Ref;
//#class
import akka.actor.*;
import akka.transactor.*;
import java.util.Set; import java.util.Set;
import scala.concurrent.stm.*;
public class UntypedCounter extends UntypedTransactor { public class FriendlyCounter extends UntypedTransactor {
Ref<Integer> count = new Ref<Integer>(0); Ref<Integer> count = Stm.ref(0);
@Override public Set<SendTo> coordinate(Object message) { @Override public Set<SendTo> coordinate(Object message) {
if (message instanceof Increment) { if (message instanceof Increment) {
@ -18,16 +22,18 @@ public class UntypedCounter extends UntypedTransactor {
return nobody(); return nobody();
} }
public void atomically(Object message) { public void atomically(InTxn txn, Object message) {
if (message instanceof Increment) { 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) { @Override public boolean normally(Object message) {
if ("GetCount".equals(message)) { if ("GetCount".equals(message)) {
getSender().tell(count.get()); getSender().tell(count.single().get());
return true; return true;
} else return false; } else return false;
} }
} }
//#class

View file

@ -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; import akka.actor.ActorRef;
public class Increment { public class Increment {
@ -19,3 +24,4 @@ public class Increment {
return friend; return friend;
} }
} }
//#class

View file

@ -0,0 +1,7 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.docs.transactor;
public class Message {}

View file

@ -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)

View file

@ -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();
}
}

View file

@ -17,6 +17,7 @@ Java API
routing routing
remoting remoting
serialization serialization
stm
agents agents
extending-akka
transactors transactors
extending-akka

60
akka-docs/java/stm.rst Normal file
View 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`.

View file

@ -1,6 +1,149 @@
.. _transactors-java: .. _transactors-java:
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.

View file

@ -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()
}
}

View file

@ -18,7 +18,8 @@ Scala API
remoting remoting
serialization serialization
fsm fsm
stm
agents agents
transactors
testing testing
extending-akka extending-akka
transactors

75
akka-docs/scala/stm.rst Normal file
View 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`.

View file

@ -1,6 +1,159 @@
.. _transactors-scala: .. _transactors-scala:
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.

View file

@ -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"
}
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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>"
}

View file

@ -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>"
}

View file

@ -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]
}

View file

@ -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
}

View file

@ -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) {}
}

View file

@ -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 + ")";
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}

View file

@ -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 + ")";
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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)
}
}
}

View file

@ -1,5 +0,0 @@
package akka.stm.test
import org.scalatest.junit.JUnitWrapperSuite
class JavaStmSpec extends JUnitWrapperSuite("akka.stm.test.JavaStmTests", Thread.currentThread.getContextClassLoader)

View file

@ -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)
}
}
}

View file

@ -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)
}
}
}

View file

@ -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)
}
}

View 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))
}

View file

@ -5,7 +5,7 @@
package akka.transactor package akka.transactor
import akka.actor.{ Actor, ActorRef } 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. * 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. * An actor with built-in support for coordinated transactions.
* *
* Transactors implement the general pattern for using [[akka.stm.Coordinated]] where * Transactors implement the general pattern for using [[akka.transactor.Coordinated]] where
* first any coordination messages are sent to other transactors, then the coordinated * coordination messages are sent to other transactors then the coordinated transaction is
* transaction is entered. * entered. Transactors can also accept explicitly sent `Coordinated` messages.
* Transactors can also accept explicitly sent `Coordinated` messages.
* <br/><br/> * <br/><br/>
* *
* Simple transactors will just implement the `atomically` method which is similar to * 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 { * class Counter extends Transactor {
* val count = Ref(0) * val count = Ref(0)
* *
* def atomically = { * def atomically = implicit txn => {
* case Increment => count alter (_ + 1) * case Increment => count transform (_ + 1)
* } * }
* } * }
* }}} * }}}
* <br/> * <br/>
* *
* To coordinate with other transactors override the `coordinate` method. * To coordinate with other transactors override the `coordinate` method.
* The `coordinate` method maps a message to a set * The `coordinate` method maps a message to a set of [[akka.actor.Transactor.SendTo]]
* of [[akka.actor.Transactor.SendTo]] objects, pairs of `ActorRef` and a message. * objects, pairs of `ActorRef` and a message.
* You can use the `include` and `sendTo` methods to easily coordinate with other transactors. * 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 `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. * 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) * case Increment => include(friend)
* } * }
* *
* def atomically = { * def atomically = implicit txn => {
* case Increment => count alter (_ + 1) * 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 * can implement normal actor behavior, or use the normal STM atomic for
* local transactions. * 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 { 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. * Implement a general pattern for using coordinated transactions.
*/ */
@ -111,12 +103,12 @@ trait Transactor extends Actor {
sendTo.actor ! coordinated(sendTo.message.getOrElse(message)) sendTo.actor ! coordinated(sendTo.message.getOrElse(message))
} }
(before orElse doNothing)(message) (before orElse doNothing)(message)
coordinated.atomic(txFactory) { (atomically orElse doNothing)(message) } coordinated.atomic { txn (atomically(txn) orElse doNothing)(message) }
(after orElse doNothing)(message) (after orElse doNothing)(message)
} }
case message { case message {
if (normally.isDefinedAt(message)) normally(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. * 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. * A Receive block that runs after the coordinated transaction.

View file

@ -5,23 +5,14 @@
package akka.transactor package akka.transactor
import akka.actor.{ UntypedActor, ActorRef } import akka.actor.{ UntypedActor, ActorRef }
import akka.stm.{ DefaultTransactionConfig, TransactionFactory }
import java.util.{ Set JSet }
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
import scala.concurrent.stm.InTxn
import java.util.{ Set JSet }
/** /**
* An UntypedActor version of transactor for using from Java. * An UntypedActor version of transactor for using from Java.
*/ */
abstract class UntypedTransactor extends UntypedActor { 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. * 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))) sendTo.actor.tell(coordinated(sendTo.message.getOrElse(message)))
} }
before(message) before(message)
coordinated.atomic(txFactory) { atomically(message) } coordinated.atomic { txn atomically(txn, message) }
after(message) after(message)
} }
case message { case message {
val normal = normally(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. * The Receive block to run inside the coordinated transaction.
*/ */
@throws(classOf[Exception]) @throws(classOf[Exception])
def atomically(message: Any) {} def atomically(txn: InTxn, message: Any) {}
/** /**
* A Receive block that runs after the coordinated transaction. * A Receive block that runs after the coordinated transaction.

View file

@ -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 class ExpectedFailureException extends RuntimeException {
public ExpectedFailureException() { public ExpectedFailureException() {

View file

@ -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 akka.actor.ActorRef;
import java.util.List; import java.util.List;

View file

@ -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.ActorRef;
import akka.actor.Actors; import akka.actor.Actors;
import akka.actor.UntypedActor; import akka.actor.UntypedActor;
import akka.stm.*; import scala.concurrent.stm.*;
import akka.util.FiniteDuration;
import org.multiverse.api.StmUtils;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class UntypedCoordinatedCounter extends UntypedActor { public class UntypedCoordinatedCounter extends UntypedActor {
private String name; private String name;
private Ref<Integer> count = new Ref<Integer>(0); private Ref<Integer> count = Stm.ref(0);
private TransactionFactory txFactory = new TransactionFactoryBuilder()
.setTimeout(new FiniteDuration(3, TimeUnit.SECONDS))
.build();
public UntypedCoordinatedCounter(String name) { public UntypedCoordinatedCounter(String name) {
this.name = name; this.name = name;
} }
private void increment() { private void increment(InTxn txn) {
//System.out.println(name + ": incrementing"); Integer newValue = count.get(txn) + 1;
count.set(count.get() + 1); count.set(newValue, txn);
} }
public void onReceive(Object incoming) throws Exception { public void onReceive(Object incoming) throws Exception {
@ -38,20 +33,24 @@ public class UntypedCoordinatedCounter extends UntypedActor {
Increment increment = (Increment) message; Increment increment = (Increment) message;
List<ActorRef> friends = increment.getFriends(); List<ActorRef> friends = increment.getFriends();
final CountDownLatch latch = increment.getLatch(); final CountDownLatch latch = increment.getLatch();
final CompletionHandler countDown = new CompletionHandler() {
public void handle(Txn.Status status) {
latch.countDown();
}
};
if (!friends.isEmpty()) { if (!friends.isEmpty()) {
Increment coordMessage = new Increment(friends.subList(1, friends.size()), latch); Increment coordMessage = new Increment(friends.subList(1, friends.size()), latch);
friends.get(0).tell(coordinated.coordinate(coordMessage)); friends.get(0).tell(coordinated.coordinate(coordMessage));
} }
coordinated.atomic(new Atomically(txFactory) { coordinated.atomic(new Atomically() {
public void atomically() { public void atomically(InTxn txn) {
increment(); increment(txn);
StmUtils.scheduleDeferredTask(new Runnable() { public void run() { latch.countDown(); } }); Stm.afterCompletion(countDown);
StmUtils.scheduleCompensatingTask(new Runnable() { public void run() { latch.countDown(); } });
} }
}); });
} }
} else if ("GetCount".equals(incoming)) { } else if ("GetCount".equals(incoming)) {
getSender().tell(count.get()); getSender().tell(count.single().get());
} }
} }
} }

View file

@ -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 static org.junit.Assert.*;
import akka.dispatch.Await;
import akka.util.Duration;
import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.Before; import org.junit.Before;
import akka.actor.ActorSystem; import akka.actor.ActorSystem;
import akka.transactor.Coordinated;
import akka.actor.ActorRef; import akka.actor.ActorRef;
import akka.actor.Props; import akka.actor.Props;
import akka.actor.UntypedActor; import akka.actor.UntypedActor;
import akka.actor.UntypedActorFactory; import akka.actor.UntypedActorFactory;
import akka.dispatch.Await;
import akka.dispatch.Future; import akka.dispatch.Future;
import akka.testkit.AkkaSpec; import akka.testkit.AkkaSpec;
import akka.testkit.EventFilter; import akka.testkit.EventFilter;
import akka.testkit.ErrorFilter; import akka.testkit.ErrorFilter;
import akka.testkit.TestEvent; import akka.testkit.TestEvent;
import akka.transactor.CoordinatedTransactionException; import akka.util.Timeout;
import java.util.Arrays; import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
@ -33,13 +34,11 @@ import scala.collection.JavaConverters;
import scala.collection.Seq; import scala.collection.Seq;
public class UntypedCoordinatedIncrementTest { public class UntypedCoordinatedIncrementTest {
ActorSystem application = ActorSystem.create("UntypedCoordinatedIncrementTest", AkkaSpec.testConf());
private static ActorSystem system; private static ActorSystem system;
@BeforeClass @BeforeClass
public static void beforeAll() { public static void beforeAll() {
system = ActorSystem.create("UntypedTransactorTest", AkkaSpec.testConf()); system = ActorSystem.create("UntypedCoordinatedIncrementTest", AkkaSpec.testConf());
} }
@AfterClass @AfterClass
@ -52,37 +51,38 @@ public class UntypedCoordinatedIncrementTest {
ActorRef failer; ActorRef failer;
int numCounters = 3; int numCounters = 3;
int timeout = 5; int timeoutSeconds = 5;
int askTimeout = 5000;
Timeout timeout = new Timeout(timeoutSeconds, TimeUnit.SECONDS);
@Before @Before
public void initialise() { public void initialise() {
Props p = new Props().withCreator(UntypedFailer.class);
counters = new ArrayList<ActorRef>(); counters = new ArrayList<ActorRef>();
for (int i = 1; i <= numCounters; i++) { for (int i = 1; i <= numCounters; i++) {
final String name = "counter" + 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() { public UntypedActor create() {
return new UntypedCoordinatedCounter(name); return new UntypedCoordinatedCounter(name);
} }
})); }));
counters.add(counter); counters.add(counter);
} }
failer = application.actorOf(p); failer = system.actorOf(new Props(UntypedFailer.class));
} }
@Test @Test
public void incrementAllCountersWithSuccessfulTransaction() { public void incrementAllCountersWithSuccessfulTransaction() {
CountDownLatch incrementLatch = new CountDownLatch(numCounters); CountDownLatch incrementLatch = new CountDownLatch(numCounters);
Increment message = new Increment(counters.subList(1, counters.size()), incrementLatch); 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 { try {
incrementLatch.await(timeout, TimeUnit.SECONDS); incrementLatch.await(timeoutSeconds, TimeUnit.SECONDS);
} catch (InterruptedException exception) { } catch (InterruptedException exception) {
} }
for (ActorRef counter : counters) { for (ActorRef counter : counters) {
Future<Object> future = counter.ask("GetCount", askTimeout); Future<Object> future = counter.ask("GetCount", timeout);
assertEquals(1, ((Integer) Await.result(future, Duration.create(timeout, TimeUnit.SECONDS))).intValue()); 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 expectedFailureFilter = (EventFilter) new ErrorFilter(ExpectedFailureException.class);
EventFilter coordinatedFilter = (EventFilter) new ErrorFilter(CoordinatedTransactionException.class); EventFilter coordinatedFilter = (EventFilter) new ErrorFilter(CoordinatedTransactionException.class);
Seq<EventFilter> ignoreExceptions = seq(expectedFailureFilter, coordinatedFilter); 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); CountDownLatch incrementLatch = new CountDownLatch(numCounters);
List<ActorRef> actors = new ArrayList<ActorRef>(counters); List<ActorRef> actors = new ArrayList<ActorRef>(counters);
actors.add(failer); actors.add(failer);
Increment message = new Increment(actors.subList(1, actors.size()), incrementLatch); 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 { try {
incrementLatch.await(timeout, TimeUnit.SECONDS); incrementLatch.await(timeoutSeconds, TimeUnit.SECONDS);
} catch (InterruptedException exception) { } catch (InterruptedException exception) {
} }
for (ActorRef counter : counters) { for (ActorRef counter : counters) {
Future<Object>future = counter.ask("GetCount", askTimeout); Future<Object>future = counter.ask("GetCount", timeout);
assertEquals(0,((Integer) Await.result(future, Duration.create(timeout, TimeUnit.SECONDS))).intValue()); int count = (Integer) Await.result(future, timeout.duration());
assertEquals(0, count);
} }
} }
public <A> Seq<A> seq(A... args) { public <A> Seq<A> seq(A... args) {
return JavaConverters.collectionAsScalaIterableConverter(Arrays.asList(args)).asScala().toSeq(); return JavaConverters.collectionAsScalaIterableConverter(Arrays.asList(args)).asScala().toSeq();
} }
@After
public void stop() {
application.shutdown();
}
} }

View file

@ -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.UntypedTransactor;
import akka.transactor.SendTo; import akka.transactor.SendTo;
import akka.actor.ActorRef; import scala.concurrent.stm.*;
import akka.stm.*;
import akka.util.FiniteDuration;
import org.multiverse.api.StmUtils;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -15,21 +15,15 @@ import java.util.concurrent.TimeUnit;
public class UntypedCounter extends UntypedTransactor { public class UntypedCounter extends UntypedTransactor {
private String name; private String name;
private Ref<Integer> count = new Ref<Integer>(0); private Ref<Integer> count = Stm.ref(0);
public UntypedCounter(String name) { public UntypedCounter(String name) {
this.name = name; this.name = name;
} }
@Override public TransactionFactory transactionFactory() { private void increment(InTxn txn) {
return new TransactionFactoryBuilder() Integer newValue = count.get(txn) + 1;
.setTimeout(new FiniteDuration(3, TimeUnit.SECONDS)) count.set(newValue, txn);
.build();
}
private void increment() {
//System.out.println(name + ": incrementing");
count.set(count.get() + 1);
} }
@Override public Set<SendTo> coordinate(Object message) { @Override public Set<SendTo> coordinate(Object message) {
@ -47,30 +41,22 @@ public class UntypedCounter extends UntypedTransactor {
} }
} }
@Override public void before(Object message) { public void atomically(InTxn txn, Object message) {
//System.out.println(name + ": before transaction");
}
public void atomically(Object message) {
if (message instanceof Increment) { if (message instanceof Increment) {
increment(); increment(txn);
final Increment increment = (Increment) message; final Increment increment = (Increment) message;
StmUtils.scheduleDeferredTask(new Runnable() { CompletionHandler countDown = new CompletionHandler() {
public void run() { increment.getLatch().countDown(); } public void handle(Txn.Status status) {
}); increment.getLatch().countDown();
StmUtils.scheduleCompensatingTask(new Runnable() { }
public void run() { increment.getLatch().countDown(); } };
}); Stm.afterCompletion(countDown);
} }
} }
@Override public void after(Object message) {
//System.out.println(name + ": after transaction");
}
@Override public boolean normally(Object message) { @Override public boolean normally(Object message) {
if ("GetCount".equals(message)) { if ("GetCount".equals(message)) {
getSender().tell(count.get()); getSender().tell(count.single().get());
return true; return true;
} else return false; } else return false;
} }

View file

@ -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();
}
}

View file

@ -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 static org.junit.Assert.*;
import akka.dispatch.Await;
import akka.util.Duration;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
@ -14,11 +16,13 @@ import akka.actor.ActorRef;
import akka.actor.Props; import akka.actor.Props;
import akka.actor.UntypedActor; import akka.actor.UntypedActor;
import akka.actor.UntypedActorFactory; import akka.actor.UntypedActorFactory;
import akka.dispatch.Await;
import akka.dispatch.Future; import akka.dispatch.Future;
import akka.testkit.AkkaSpec;
import akka.testkit.EventFilter; import akka.testkit.EventFilter;
import akka.testkit.ErrorFilter; import akka.testkit.ErrorFilter;
import akka.testkit.TestEvent; import akka.testkit.TestEvent;
import akka.transactor.CoordinatedTransactionException; import akka.util.Timeout;
import java.util.Arrays; import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
@ -28,7 +32,6 @@ import java.util.concurrent.TimeUnit;
import scala.collection.JavaConverters; import scala.collection.JavaConverters;
import scala.collection.Seq; import scala.collection.Seq;
import akka.testkit.AkkaSpec;
public class UntypedTransactorTest { public class UntypedTransactorTest {
@ -49,22 +52,23 @@ public class UntypedTransactorTest {
ActorRef failer; ActorRef failer;
int numCounters = 3; int numCounters = 3;
int timeout = 5; int timeoutSeconds = 5;
int askTimeout = 5000;
Timeout timeout = new Timeout(timeoutSeconds, TimeUnit.SECONDS);
@Before @Before
public void initialise() { public void initialise() {
counters = new ArrayList<ActorRef>(); counters = new ArrayList<ActorRef>();
for (int i = 1; i <= numCounters; i++) { for (int i = 1; i <= numCounters; i++) {
final String name = "counter" + 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() { public UntypedActor create() {
return new UntypedCounter(name); return new UntypedCounter(name);
} }
})); }));
counters.add(counter); counters.add(counter);
} }
failer = system.actorOf(new Props().withCreator(UntypedFailer.class)); failer = system.actorOf(new Props(UntypedFailer.class));
} }
@Test @Test
@ -73,12 +77,12 @@ public class UntypedTransactorTest {
Increment message = new Increment(counters.subList(1, counters.size()), incrementLatch); Increment message = new Increment(counters.subList(1, counters.size()), incrementLatch);
counters.get(0).tell(message); counters.get(0).tell(message);
try { try {
incrementLatch.await(timeout, TimeUnit.SECONDS); incrementLatch.await(timeoutSeconds, TimeUnit.SECONDS);
} catch (InterruptedException exception) { } catch (InterruptedException exception) {
} }
for (ActorRef counter : counters) { for (ActorRef counter : counters) {
Future<Object> future = counter.ask("GetCount", askTimeout); Future<Object> future = counter.ask("GetCount", timeout);
int count = (Integer) Await.result(future, Duration.create(askTimeout, TimeUnit.MILLISECONDS)); int count = (Integer) Await.result(future, timeout.duration());
assertEquals(1, count); assertEquals(1, count);
} }
} }
@ -95,12 +99,12 @@ public class UntypedTransactorTest {
Increment message = new Increment(actors.subList(1, actors.size()), incrementLatch); Increment message = new Increment(actors.subList(1, actors.size()), incrementLatch);
actors.get(0).tell(message); actors.get(0).tell(message);
try { try {
incrementLatch.await(timeout, TimeUnit.SECONDS); incrementLatch.await(timeoutSeconds, TimeUnit.SECONDS);
} catch (InterruptedException exception) { } catch (InterruptedException exception) {
} }
for (ActorRef counter : counters) { for (ActorRef counter : counters) {
Future<Object> future = counter.ask("GetCount", askTimeout); Future<Object> future = counter.ask("GetCount", timeout);
int count = (Integer) Await.result(future, Duration.create(askTimeout, TimeUnit.MILLISECONDS)); int count = (Integer) Await.result(future, timeout.duration());
assertEquals(0, count); assertEquals(0, count);
} }
} }

View file

@ -1,14 +1,17 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.transactor package akka.transactor
import org.scalatest.BeforeAndAfterAll import org.scalatest.BeforeAndAfterAll
import akka.actor.ActorSystem
import akka.actor._ import akka.actor._
import akka.stm.{ Ref, TransactionFactory } import akka.dispatch.Await
import akka.util.duration._ import akka.util.duration._
import akka.util.Timeout import akka.util.Timeout
import akka.testkit._ import akka.testkit._
import akka.dispatch.Await import scala.concurrent.stm._
object CoordinatedIncrement { object CoordinatedIncrement {
case class Increment(friends: Seq[ActorRef]) case class Increment(friends: Seq[ActorRef])
@ -17,34 +20,27 @@ object CoordinatedIncrement {
class Counter(name: String) extends Actor { class Counter(name: String) extends Actor {
val count = Ref(0) val count = Ref(0)
implicit val txFactory = TransactionFactory(timeout = 3 seconds)
def increment = {
count alter (_ + 1)
}
def receive = { def receive = {
case coordinated @ Coordinated(Increment(friends)) { case coordinated @ Coordinated(Increment(friends)) {
if (friends.nonEmpty) { if (friends.nonEmpty) {
friends.head ! coordinated(Increment(friends.tail)) friends.head ! coordinated(Increment(friends.tail))
} }
coordinated atomic { coordinated.atomic { implicit t
increment count transform (_ + 1)
} }
} }
case GetCount sender ! count.get case GetCount sender ! count.single.get
} }
} }
class ExpectedFailureException extends RuntimeException("Expected failure") class ExpectedFailureException extends RuntimeException("Expected failure")
class Failer extends Actor { class Failer extends Actor {
val txFactory = TransactionFactory(timeout = 3 seconds)
def receive = { def receive = {
case coordinated @ Coordinated(Increment(friends)) { case coordinated @ Coordinated(Increment(friends)) {
coordinated.atomic(txFactory) { coordinated.atomic { t
throw new ExpectedFailureException throw new ExpectedFailureException
} }
} }

View file

@ -1,21 +1,23 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.transactor package akka.transactor
import org.scalatest.WordSpec
import org.scalatest.matchers.MustMatchers
import org.scalatest.BeforeAndAfterAll import org.scalatest.BeforeAndAfterAll
import akka.actor.ActorSystem
import akka.actor._ import akka.actor._
import akka.util.Timeout import akka.dispatch.Await
import akka.stm._
import akka.util.duration._ import akka.util.duration._
import akka.util.Timeout
import akka.testkit._ import akka.testkit._
import akka.testkit.TestEvent.Mute
import scala.concurrent.stm._
import scala.util.Random.{ nextInt random } import scala.util.Random.{ nextInt random }
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import akka.testkit.TestEvent.Mute
import akka.dispatch.Await
object FickleFriends { 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 class Increment(friends: Seq[ActorRef])
case object GetCount case object GetCount
@ -25,24 +27,22 @@ object FickleFriends {
class Coordinator(name: String) extends Actor { class Coordinator(name: String) extends Actor {
val count = Ref(0) val count = Ref(0)
implicit val txFactory = TransactionFactory(timeout = 3 seconds) def increment(implicit txn: InTxn) = {
count transform (_ + 1)
def increment = {
count alter (_ + 1)
} }
def receive = { def receive = {
case FriendlyIncrement(friends, latch) { case FriendlyIncrement(friends, timeout, latch) {
var success = false var success = false
while (!success) { while (!success) {
try { try {
val coordinated = Coordinated() val coordinated = Coordinated()(timeout)
if (friends.nonEmpty) { if (friends.nonEmpty) {
friends.head ! coordinated(Increment(friends.tail)) friends.head ! coordinated(Increment(friends.tail))
} }
coordinated atomic { coordinated.atomic { implicit t
increment increment
deferred { Txn.afterCommit { status
success = true success = true
latch.countDown() 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 { class FickleCounter(name: String) extends Actor {
val count = Ref(0) val count = Ref(0)
implicit val txFactory = TransactionFactory(timeout = 3 seconds) val maxFailures = 3
var failures = 0
def increment = { def increment(implicit txn: InTxn) = {
count alter (_ + 1) count transform (_ + 1)
} }
def failIf(x: Int, y: Int) = { 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 = { def receive = {
@ -83,14 +87,14 @@ object FickleFriends {
friends.head ! coordinated(Increment(friends.tail)) friends.head ! coordinated(Increment(friends.tail))
} }
failIf(failAt, 1) failIf(failAt, 1)
coordinated atomic { coordinated.atomic { implicit t
failIf(failAt, 2) failIf(failAt, 2)
increment increment
failIf(failAt, 3) 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)) system.eventStream.publish(Mute(ignoreExceptions))
val (counters, coordinator) = actorOfs val (counters, coordinator) = actorOfs
val latch = new CountDownLatch(1) val latch = new CountDownLatch(1)
coordinator ! FriendlyIncrement(counters, latch) coordinator ! FriendlyIncrement(counters, timeout, latch)
latch.await // this could take a while latch.await // this could take a while
Await.result(coordinator ? GetCount, timeout.duration) must be === 1 Await.result(coordinator ? GetCount, timeout.duration) must be === 1
for (counter counters) { for (counter counters) {

View file

@ -1,7 +1,11 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.transactor package akka.transactor
import org.scalatest.junit.JUnitWrapperSuite import org.scalatest.junit.JUnitWrapperSuite
class JavaUntypedCoordinatedSpec extends JUnitWrapperSuite( class JavaUntypedCoordinatedSpec extends JUnitWrapperSuite(
"akka.transactor.test.UntypedCoordinatedIncrementTest", "akka.transactor.UntypedCoordinatedIncrementTest",
Thread.currentThread.getContextClassLoader) Thread.currentThread.getContextClassLoader)

View file

@ -1,7 +1,11 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.transactor package akka.transactor
import org.scalatest.junit.JUnitWrapperSuite import org.scalatest.junit.JUnitWrapperSuite
class JavaUntypedTransactorSpec extends JUnitWrapperSuite( class JavaUntypedTransactorSpec extends JUnitWrapperSuite(
"akka.transactor.test.UntypedTransactorTest", "akka.transactor.UntypedTransactorTest",
Thread.currentThread.getContextClassLoader) Thread.currentThread.getContextClassLoader)

View file

@ -1,15 +1,15 @@
/**
* Copyright (C) 2009-2011 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.transactor package akka.transactor
import org.scalatest.WordSpec
import org.scalatest.matchers.MustMatchers
import akka.actor.ActorSystem
import akka.actor._ import akka.actor._
import akka.util.Timeout
import akka.stm._
import akka.util.duration._
import akka.testkit._
import akka.dispatch.Await import akka.dispatch.Await
import akka.util.duration._
import akka.util.Timeout
import akka.testkit._
import scala.concurrent.stm._
object TransactorIncrement { object TransactorIncrement {
case class Increment(friends: Seq[ActorRef], latch: TestLatch) case class Increment(friends: Seq[ActorRef], latch: TestLatch)
@ -18,10 +18,8 @@ object TransactorIncrement {
class Counter(name: String) extends Transactor { class Counter(name: String) extends Transactor {
val count = Ref(0) val count = Ref(0)
override def transactionFactory = TransactionFactory(timeout = 3 seconds) def increment(implicit txn: InTxn) = {
count transform (_ + 1)
def increment = {
count alter (_ + 1)
} }
override def coordinate = { override def coordinate = {
@ -35,11 +33,10 @@ object TransactorIncrement {
case i: Increment case i: Increment
} }
def atomically = { def atomically = implicit txn {
case Increment(friends, latch) { case Increment(friends, latch) {
increment increment
deferred { latch.countDown() } Txn.afterCompletion { status latch.countDown() }
compensating { latch.countDown() }
} }
} }
@ -48,14 +45,14 @@ object TransactorIncrement {
} }
override def normally = { override def normally = {
case GetCount sender ! count.get case GetCount sender ! count.single.get
} }
} }
class ExpectedFailureException extends RuntimeException("Expected failure") class ExpectedFailureException extends RuntimeException("Expected failure")
class Failer extends Transactor { class Failer extends Transactor {
def atomically = { def atomically = implicit txn {
case _ throw new ExpectedFailureException case _ throw new ExpectedFailureException
} }
} }
@ -65,10 +62,10 @@ object SimpleTransactor {
case class Set(ref: Ref[Int], value: Int, latch: TestLatch) case class Set(ref: Ref[Int], value: Int, latch: TestLatch)
class Setter extends Transactor { class Setter extends Transactor {
def atomically = { def atomically = implicit txn {
case Set(ref, value, latch) { case Set(ref, value, latch) {
ref.set(value) ref() = value
deferred { latch.countDown() } Txn.afterCompletion { status latch.countDown() }
} }
} }
} }
@ -95,7 +92,7 @@ class TransactorSpec extends AkkaSpec {
val (counters, failer) = createTransactors val (counters, failer) = createTransactors
val incrementLatch = TestLatch(numCounters) val incrementLatch = TestLatch(numCounters)
counters(0) ! Increment(counters.tail, incrementLatch) counters(0) ! Increment(counters.tail, incrementLatch)
incrementLatch.await Await.ready(incrementLatch, 5 seconds)
for (counter counters) { for (counter counters) {
Await.result(counter ? GetCount, timeout.duration) must be === 1 Await.result(counter ? GetCount, timeout.duration) must be === 1
} }
@ -112,7 +109,7 @@ class TransactorSpec extends AkkaSpec {
val (counters, failer) = createTransactors val (counters, failer) = createTransactors
val failLatch = TestLatch(numCounters) val failLatch = TestLatch(numCounters)
counters(0) ! Increment(counters.tail :+ failer, failLatch) counters(0) ! Increment(counters.tail :+ failer, failLatch)
failLatch.await Await.ready(failLatch, 5 seconds)
for (counter counters) { for (counter counters) {
Await.result(counter ? GetCount, timeout.duration) must be === 0 Await.result(counter ? GetCount, timeout.duration) must be === 0
} }
@ -128,8 +125,8 @@ class TransactorSpec extends AkkaSpec {
val ref = Ref(0) val ref = Ref(0)
val latch = TestLatch(1) val latch = TestLatch(1)
transactor ! Set(ref, 5, latch) transactor ! Set(ref, 5, latch)
latch.await Await.ready(latch, 5 seconds)
val value = atomic { ref.get } val value = ref.single.get
value must be === 5 value must be === 5
system.stop(transactor) system.stop(transactor)
} }

View file

@ -30,7 +30,7 @@ object AkkaBuild extends Build {
Unidoc.unidocExclude := Seq(samples.id, tutorials.id), Unidoc.unidocExclude := Seq(samples.id, tutorials.id),
Dist.distExclude := Seq(actorTests.id, akkaSbtPlugin.id, docs.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( 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( // lazy val amqp = Project(
// id = "akka-amqp", // id = "akka-amqp",
// base = file("akka-amqp"), // base = file("akka-amqp"),
@ -265,7 +274,7 @@ object AkkaBuild extends Build {
lazy val docs = Project( lazy val docs = Project(
id = "akka-docs", id = "akka-docs",
base = file("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( settings = defaultSettings ++ Seq(
unmanagedSourceDirectories in Test <<= baseDirectory { _ ** "code" get }, unmanagedSourceDirectories in Test <<= baseDirectory { _ ** "code" get },
libraryDependencies ++= Dependencies.docs, libraryDependencies ++= Dependencies.docs,
@ -380,6 +389,8 @@ object Dependencies {
val agent = Seq(scalaStm, Test.scalatest, Test.junit) val agent = Seq(scalaStm, Test.scalatest, Test.junit)
val transactor = Seq(scalaStm, Test.scalatest, Test.junit)
val amqp = Seq(rabbit, commonsIo, protobuf) val amqp = Seq(rabbit, commonsIo, protobuf)
val mailboxes = Seq(Test.scalatest, Test.junit) val mailboxes = Seq(Test.scalatest, Test.junit)