Skip to content

Commit

Permalink
Merge pull request #4261 from mmusgrov/issue4260
Browse files Browse the repository at this point in the history
Add an extension supporting Narayana STM applications
  • Loading branch information
stuartwdouglas authored Oct 1, 2019
2 parents 512dc59 + 983bc07 commit fb92b20
Show file tree
Hide file tree
Showing 22 changed files with 715 additions and 1 deletion.
1 change: 1 addition & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ stages:
- mongodb-client
- mongodb-panache
- neo4j
- narayana-stm
name: data

- template: ci-templates/native-build-steps.yaml
Expand Down
6 changes: 5 additions & 1 deletion bom/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,11 @@
<artifactId>quarkus-test-maven</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-narayana-stm-deployment</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
10 changes: 10 additions & 0 deletions bom/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1479,6 +1479,11 @@
<artifactId>narayana-jts-integration</artifactId>
<version>${narayana.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.narayana.stm</groupId>
<artifactId>stm</artifactId>
<version>${narayana.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-core</artifactId>
Expand Down Expand Up @@ -2343,6 +2348,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-narayana-stm</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public final class FeatureBuildItem extends MultiBuildItem {
public static final String MONGODB_CLIENT = "mongodb-client";
public static final String MONGODB_PANACHE = "mongodb-panache";
public static final String NARAYANA_JTA = "narayana-jta";
public static final String NARAYANA_STM = "narayana-stm";
public static final String REACTIVE_PG_CLIENT = "reactive-pg-client";
public static final String REACTIVE_MYSQL_CLIENT = "reactive-mysql-client";
public static final String NEO4J = "neo4j";
Expand Down
16 changes: 16 additions & 0 deletions devtools/common/src/main/filtered/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,22 @@
"artifactId": "quarkus-narayana-jta",
"guide": "https://quarkus.io/guides/transaction-guide"
},
{
"name": "Narayana STM - Software Transactional Memory",
"labels": [
"narayana-stm",
"narayana",
"stm",
"transactions",
"transaction",
"software-transactional-memory",
"tx",
"txs"
],
"groupId": "io.quarkus",
"artifactId": "quarkus-narayana-stm",
"guide": "https://quarkus.io/guides/stm-guide"
},
{
"name": "Neo4j client",
"labels": [
Expand Down
1 change: 1 addition & 0 deletions docs/src/main/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ include::quarkus-intro.adoc[tag=intro]
* link:websocket-guide.html[Using Websockets]
* link:validation-guide.html[Validation with Hibernate Validator]
* link:transaction-guide.html[Using Transactions]
* link:stm-guide.html[Using Software Transactional Memory]
* link:hibernate-orm-guide.html[Using Hibernate ORM]
* link:hibernate-orm-panache-guide.html[Simplified Hibernate ORM with Panache]
* link:rest-client-guide.html[Using REST Client]
Expand Down
148 changes: 148 additions & 0 deletions docs/src/main/asciidoc/stm-guide.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
////
This guide is maintained in the main Quarkus repository
and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc
////
= Using Software Transactional Memory in Quarkus

include::./attributes.adoc[]

Quarkus supports the Software Transactional Memory (STM) implementation provided by the
Narayana open source project. Narayana STM allows a program to group object accesses into
a transaction such that other transactions either see all of the changes at once or they
see none of them.

STM offers an approach to developing transactional applications in a highly concurrent
environment with some of the same characteristics of ACID (Atomicity, Consistency,
Isolation and Durability) transactions.

To use Narayana STM you must define which objects you would like to be transactional using java
annotations. Please refer to the https://narayana.io/docs/project/index.html#d0e16066[Narayana STM manual]
and the https://narayana.io//docs/project/index.html#d0e16133[STM annotations guide] for more details.

There is also a fully worked example in the quickstarts which you may access by cloning the
Git repository: `git clone {quickstarts-clone-url}`, or by downloading an {quickstarts-archive-url}[archive].
Look for the `using-stm` example.

== Setting it up

To use the extension include it as a dependency in your application pom:

[source,xml]
--
<dependencies>
<!-- STM extension -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-narayana-stm</artifactId>
<version>${quarkus.version}</version>
</dependency>
</dependencies>
--

Now you may use the STM library just like you would normally use it. But briefly, the process is:

== Defining Transactional Objects

Transactional objects must implement an interface. Add the `org.jboss.stm.annotations.Transactional` annotation to the
interfaces that you wish to be managed by a transactional container. For example

[source,java]
--
@Transactional
public interface FlightService {
int getNumberOfBookings();
void makeBooking(String details);
}
--

Unless specified using other annotations, all public methods of implementations of this object will be assumed to modify the state of the object.
Please refer to the Narayana guide for details of how to exert finer grained control over the transactional behaviour of objects that implement
interfaces marked with the `@Transactional` annotation.

== Creating STM objects

The STM library needs to be told which objects it should be managing by providing a container for the transactional memory.
The default container (`org.jboss.stm.Container`) provides support for volatile objects that cannot be shared between JVMs
(although other constructors do allow for other models):

[source,java]
--
import org.jboss.stm.Container;

...

Container<FlightService> container = new Container<>(); <1>
FlightServiceImpl instance = new FlightServiceImpl(); <2>
FlightService flightServiceProxy = container.create(instance); <3>
--

<1> You need to tell each Container about the type of objects for which it will be responsible. In this example
it will be instances that implement the FlightService interface.
<2> Then you create an instance that implements FlightService. You cannot use it directly at this stage because
its operations aren't being monitored by the Container.
<3> To obtain a managed instance, pass the instance to the `container` to obtain a reference through which you
will be able perform transactional operations. This reference can be used safely from multiple threads
(provided that each thread uses it in a transaction context - see the next section.

== Defining transaction boundaries

STM objects must be accessed within a transaction (otherwise they will behave just like any other java object).
You can define the transaction boundary in two ways:

=== Declarative approach

The easiest, but less flexible, way to define your transaction boundaries is to place an `@NestedTopLevel` or `@Nested` annotation on the interface.
Then when a method of your transactional object is invoked a new transaction will be created for the duration of the method call.

=== API approach

The more flexible approach is to manually start a transaction before accessing methods of transactional objects:

[source,java]
--
AtomicAction aa = new AtomicAction(); <1>

aa.begin(); <2>
{
try {
flightService.makeBooking("BA123 ...");
taxiService.makeBooking("East Coast Taxis ..."); <3>
<4>
aa.commit();
<5>
} catch (Exception e) {
aa.abort(); <6>
}
}
--

<1> An object for manually controlling transaction boundaries (AtomicAction and many other useful
classes are included in the extension).
Refer https://narayana.io//docs/api/com/arjuna/ats/arjuna/AtomicAction.html[to the javadoc] for more detail.
<2> Programmatically begin a transaction.
<3> Notice that object updates can be composed which means that updates to multiple objects can be committed together as a single action.
[Note that it is also possible to begin nested transactions so that you can perform speculative work which may then be abandoned
without abandoning other work performed by the outer transaction].
<4> Since the transaction has not yet been committed the changes made by the flight and taxi services are not visible outside of the transaction.
<5> Since the commit was successful the changes made by the flight and taxi services are now visible to other threads.
Note that other transactions that relied on the old state may or may not now incur conflicts when they commit (the STM library
provides a number of features for managing conflicting behaviour and these are covered in the Narayana STM manual).
<6> Programmatically decide to abort the transaction which means that the changes made by the flight and taxi services are discarded.

== Concurrency behaviour

The goal of STM is to simplify object reads and writes from multiple threads.
Threads are responsible for starting their own transactions before accessing
a transactional object. The STM library will safely manage any conflicts between
these threads. For example, if the access mode is pessimistic (the default),
and a thread enters a transactional method then other threads may be blocked
until the first thread leaves the method. This blocking behaviour may be modified
using suitable STM annotations. Optimistic concurrency is also supported: in
this mode conflicts are checked only at commit time and a thread may be forced
to abort if another thread has made conflicting updates.

Remark: sharing a transaction between multiple threads is possible but is currently
an advanced use case only and the Narayana documentation should be consulted
if this behaviour is required. In particular, STM does not yet support the features
described in the link:context-propagation-guide.html[Context Propagation guide].
49 changes: 49 additions & 0 deletions extensions/narayana-stm/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-narayana-stm-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-narayana-stm-deployment</artifactId>
<name>Quarkus - Narayana STM - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-narayana-stm</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.quarkus.narayana.stm.deployment;

import java.util.ArrayList;
import java.util.Collection;

import javax.inject.Inject;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
import org.jboss.stm.annotations.Transactional;

import com.arjuna.ats.internal.arjuna.coordinator.CheckedActionFactoryImple;
import com.arjuna.ats.txoj.Lock;

import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.substrate.ReflectiveHierarchyBuildItem;
import io.quarkus.deployment.builditem.substrate.SubstrateProxyDefinitionBuildItem;

class NarayanaStmProcessor {
private static final Logger log = Logger.getLogger(NarayanaStmProcessor.class.getName());

@Inject
CombinedIndexBuildItem combinedIndexBuildItem;

@Inject
BuildProducer<ReflectiveHierarchyBuildItem> reflectiveHierarchyClass;

@Inject
BuildProducer<ReflectiveClassBuildItem> reflectiveClass;

// register classes in need of reflection
@BuildStep
ReflectiveClassBuildItem register(BuildProducer<FeatureBuildItem> feature) {
feature.produce(new FeatureBuildItem(FeatureBuildItem.NARAYANA_STM));

return new ReflectiveClassBuildItem(true, false,
CheckedActionFactoryImple.class.getName(),
Lock.class.getName());
}

// register STM dynamic proxies
@BuildStep
SubstrateProxyDefinitionBuildItem stmProxies() {
final DotName TRANSACTIONAL = DotName.createSimple(Transactional.class.getName());
IndexView index = combinedIndexBuildItem.getIndex();
Collection<String> proxies = new ArrayList<>();

for (AnnotationInstance stm : index.getAnnotations(TRANSACTIONAL)) {
if (AnnotationTarget.Kind.CLASS.equals(stm.target().kind())) {
DotName name = stm.target().asClass().name();

proxies.add(name.toString());

log.debugf("Registering transactional interface %s%n", name);

for (ClassInfo ci : index.getAllKnownImplementors(name)) {
reflectiveHierarchyClass.produce(
new ReflectiveHierarchyBuildItem(Type.create(ci.name(), Type.Kind.CLASS)));
}
}
}

String[] classNames = proxies.toArray(new String[0]);

reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, classNames));

return new SubstrateProxyDefinitionBuildItem(classNames);
}
}
20 changes: 20 additions & 0 deletions extensions/narayana-stm/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-build-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../../build-parent/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-narayana-stm-parent</artifactId>
<name>Quarkus - Narayana STM</name>
<packaging>pom</packaging>
<modules>
<module>deployment</module>
<module>runtime</module>
</modules>
</project>
Loading

0 comments on commit fb92b20

Please sign in to comment.