This is an automated email from the ASF dual-hosted git repository.
colegreer pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/master by this push:
new d454934391 Add extra testing around transaction isolation levels and
update documentation CTR. (#2173)
d454934391 is described below
commit d4549343916b742f19a861cbb6fcdde028b019cb
Author: kenhuuu <[email protected]>
AuthorDate: Thu Jul 27 16:43:28 2023 -0700
Add extra testing around transaction isolation levels and update
documentation CTR. (#2173)
---
.../reference/implementations-tinkergraph.asciidoc | 7 +-
docs/src/upgrade/release-3.7.x.asciidoc | 2 +
.../structure/TransactionMultiThreadedTest.java | 41 +++-
.../structure/TinkerTransactionGraphTest.java | 244 ++++++++++++++++++++-
4 files changed, 288 insertions(+), 6 deletions(-)
diff --git a/docs/src/reference/implementations-tinkergraph.asciidoc
b/docs/src/reference/implementations-tinkergraph.asciidoc
index 893a1039ee..2ddba9e28e 100644
--- a/docs/src/reference/implementations-tinkergraph.asciidoc
+++ b/docs/src/reference/implementations-tinkergraph.asciidoc
@@ -210,8 +210,11 @@ TinkerTransactionGraph only has support for ThreadLocal
transactions, so embedde
supported. You can think of the transaction as belonging to a thread, any
traversals executed within the same thread
will share the same transaction even if you attempt to start a new transaction.
-TinkerTransactionGraph implements `read committed` isolation level, so it only
guards against dirty reads.
-Additionally, it employs optimistic locking as its locking strategy. This
reduces complexity in the design as
+TinkerTransactionGraph provides the `read committed` transaction isolation
level. This means that it will always try to
+guard against dirty reads. While you may notice stricter isolation semantics
in some cases, you should not depend on
+this behavior as it may change in the future.
+
+TinkerTransactionGraph employs optimistic locking as its locking strategy.
This reduces complexity in the design as
there are fewer timeouts that the user needs to manage. However, a consequence
of this approach is that a transaction
will throw a `TransactionException` if two different transactions attempt to
lock the same element (see "Best Practices"
below).
diff --git a/docs/src/upgrade/release-3.7.x.asciidoc
b/docs/src/upgrade/release-3.7.x.asciidoc
index 83e7f1c51d..c0c4c68370 100644
--- a/docs/src/upgrade/release-3.7.x.asciidoc
+++ b/docs/src/upgrade/release-3.7.x.asciidoc
@@ -458,6 +458,8 @@ the JVM. The following are only examples used by
TinkerPop's automated tests and
--add-opens=java.base/java.net=ALL-UNNAMED
----
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2703[TINKERPOP-2703]
+
=== Upgrading for Providers
==== Graph Driver Providers
diff --git
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/TransactionMultiThreadedTest.java
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/TransactionMultiThreadedTest.java
index ee57728571..17f399e0f3 100644
---
a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/TransactionMultiThreadedTest.java
+++
b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/structure/TransactionMultiThreadedTest.java
@@ -24,6 +24,8 @@ import
org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSo
import org.apache.tinkerpop.gremlin.structure.util.TransactionException;
import org.junit.Test;
+import java.util.concurrent.atomic.AtomicLong;
+
import static
org.apache.tinkerpop.gremlin.structure.Graph.Features.EdgeFeatures.FEATURE_ADD_EDGES;
import static
org.apache.tinkerpop.gremlin.structure.Graph.Features.EdgeFeatures.FEATURE_REMOVE_EDGES;
import static
org.apache.tinkerpop.gremlin.structure.Graph.Features.ElementFeatures.FEATURE_ADD_PROPERTY;
@@ -526,6 +528,36 @@ public class TransactionMultiThreadedTest extends
AbstractGremlinTest {
countElementsInNewThreadTx(g, 0, 0);
}
+ @Test
+ @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class,
feature = FEATURE_TRANSACTIONS)
+ @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class,
feature = FEATURE_ADD_VERTICES)
+ @FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class,
feature = FEATURE_ADD_PROPERTY)
+ public void
shouldHandleAddingPropertyWhenOtherTxAttemptsDeleteThenRollsback() throws
InterruptedException {
+ final GraphTraversalSource gtx = g.tx().begin();
+
+ final Vertex v1 = gtx.addV().next();
+ gtx.tx().commit();
+
+ // tx1 try to add property
+ gtx.V(v1.id()).property("test", 1).iterate();
+
+ // tx2 in same time delete vertex used by tx1
+ final Thread thread = new Thread(() -> {
+ final GraphTraversalSource gtx2 = g.tx().begin();
+ gtx2.V(v1.id()).drop().iterate();
+ gtx2.tx().rollback();
+ });
+ thread.start();
+ thread.join();
+
+ gtx.tx().commit();
+
+ assertEquals(1, (long) gtx.V().count().next());
+ assertEquals(1, gtx.V(v1.id()).values("test").next());
+
+ countElementsInNewThreadTx(g, 1, 0);
+ }
+
@Test
@FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class,
feature = FEATURE_TRANSACTIONS)
@FeatureRequirement(featureClass = Graph.Features.VertexFeatures.class,
feature = FEATURE_ADD_VERTICES)
@@ -663,12 +695,17 @@ public class TransactionMultiThreadedTest extends
AbstractGremlinTest {
}
private void countElementsInNewThreadTx(final GraphTraversalSource g,
final long verticesCount, final long edgesCount) throws InterruptedException {
+ final AtomicLong vCount = new AtomicLong(-1);
+ final AtomicLong eCount = new AtomicLong(-1);
final Thread thread = new Thread(() -> {
final GraphTraversalSource gtx = g.tx().begin();
- assertEquals(verticesCount, (long) gtx.V().count().next());
- assertEquals(edgesCount, (long) gtx.E().count().next());
+ vCount.set(gtx.V().count().next());
+ eCount.set(gtx.E().count().next());
});
thread.start();
thread.join();
+
+ assertEquals(verticesCount, vCount.get());
+ assertEquals(edgesCount, eCount.get());
}
}
diff --git
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraphTest.java
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraphTest.java
index 3bc8874fb5..3ac5c30754 100644
---
a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraphTest.java
+++
b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerTransactionGraphTest.java
@@ -29,6 +29,7 @@ import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -723,12 +724,251 @@ public class TinkerTransactionGraphTest {
// utility methods
private void countElementsInNewThreadTx(final TinkerTransactionGraph g,
final long verticesCount, final long edgesCount) throws InterruptedException {
+ final AtomicLong vCount = new AtomicLong(-1);
+ final AtomicLong eCount = new AtomicLong(-1);
final Thread thread = new Thread(() -> {
final GraphTraversalSource gtx = g.tx().begin();
- assertEquals(verticesCount, (long) gtx.V().count().next());
- assertEquals(edgesCount, (long) gtx.E().count().next());
+ vCount.set(gtx.V().count().next());
+ eCount.set(gtx.E().count().next());
});
thread.start();
thread.join();
+
+ assertEquals(verticesCount, vCount.get());
+ assertEquals(edgesCount, eCount.get());
+ }
+
+ @Test
+ public void shouldNotAllowDirtyReadsOfVertexForReadOnlyTransaction()
throws InterruptedException {
+ final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+ final GraphTraversalSource gtx = g.tx().begin();
+
+ gtx.addV().next();
+
+ final AtomicLong gtx2Read = new AtomicLong(-1);
+ final Thread thread = new Thread(() -> {
+ final GraphTraversalSource gtx2 = g.tx().begin();
+
+ gtx2Read.set(gtx2.V().count().next());
+ });
+ thread.start();
+ thread.join();
+
+ assertEquals(0, gtx2Read.get());
+ assertEquals(1, (long) gtx.V().count().next());
+ }
+
+ @Test
+ public void shouldNotAllowDirtyReadsOfVertexDropForReadOnlyTransaction()
throws InterruptedException {
+ final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+ final GraphTraversalSource gtx = g.tx().begin();
+
+ gtx.addV().next();
+ gtx.tx().commit();
+
+ gtx.V().drop().iterate();
+
+ final AtomicLong gtx2Read = new AtomicLong(-1);
+ final Thread thread = new Thread(() -> {
+ final GraphTraversalSource gtx2 = g.tx().begin();
+
+ gtx2Read.set(gtx2.V().count().next());
+ });
+ thread.start();
+ thread.join();
+
+ assertEquals(1, gtx2Read.get());
+ assertEquals(0, (long) gtx.V().count().next());
+ }
+
+ @Test
+ public void shouldNotAllowDirtyReadsOfEdgeForReadOnlyTransaction() throws
InterruptedException {
+ final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+ final GraphTraversalSource gtx = g.tx().begin();
+
+ final Vertex v1 = gtx.addV().next();
+ final Vertex v2 = gtx.addV().next();
+ gtx.addE("test").from(v1).to(v2).iterate();
+
+ final AtomicLong gtx2Read = new AtomicLong(-1);
+ final Thread thread = new Thread(() -> {
+ final GraphTraversalSource gtx2 = g.tx().begin();
+
+ gtx2Read.set(gtx2.E().count().next());
+ });
+ thread.start();
+ thread.join();
+
+ assertEquals(0, gtx2Read.get());
+ assertEquals(1, (long) gtx.E().count().next());
+ }
+
+ @Test
+ public void shouldNotAllowDirtyReadsOfEdgeDropForReadOnlyTransaction()
throws InterruptedException {
+ final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+ final GraphTraversalSource gtx = g.tx().begin();
+
+ final Vertex v1 = gtx.addV().next();
+ final Vertex v2 = gtx.addV().next();
+ gtx.addE("test").from(v1).to(v2).iterate();
+ gtx.tx().commit();
+
+ gtx.E().drop().iterate();
+
+ final AtomicLong gtx2Read = new AtomicLong(-1);
+ final Thread thread = new Thread(() -> {
+ final GraphTraversalSource gtx2 = g.tx().begin();
+
+ gtx2Read.set(gtx2.E().count().next());
+ });
+ thread.start();
+ thread.join();
+
+ assertEquals(1, gtx2Read.get());
+ assertEquals(0, (long) gtx.E().count().next());
+ }
+
+ @Test
+ public void
shouldNotAllowDirtyReadsOfVertexPropertyAddForReadOnlyTransaction() throws
InterruptedException {
+ final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+ final GraphTraversalSource gtx = g.tx().begin();
+
+ gtx.addV().property("a", 1).iterate();
+ gtx.tx().commit();
+
+ gtx.V().property("b", 2).iterate();
+
+ final AtomicLong gtx2Read = new AtomicLong(-1);
+ final Thread thread = new Thread(() -> {
+ final GraphTraversalSource gtx2 = g.tx().begin();
+
+ gtx2Read.set(gtx2.V().properties().count().next());
+ });
+ thread.start();
+ thread.join();
+
+ assertEquals(1, gtx2Read.get());
+ assertEquals(2, (long) gtx.V().properties().count().next());
+ }
+
+ @Test
+ public void
shouldNotAllowDirtyReadsOfVertexPropertyDropForReadOnlyTransaction() throws
InterruptedException {
+ final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+ final GraphTraversalSource gtx = g.tx().begin();
+
+ gtx.addV().property("a", 1).iterate();
+ gtx.tx().commit();
+
+ gtx.V().properties().drop().iterate();
+
+ final AtomicLong gtx2Read = new AtomicLong(-1);
+ final Thread thread = new Thread(() -> {
+ final GraphTraversalSource gtx2 = g.tx().begin();
+
+ gtx2Read.set(gtx2.V().properties().count().next());
+ });
+ thread.start();
+ thread.join();
+
+ assertEquals(1, gtx2Read.get());
+ assertEquals(0, (long) gtx.V().properties().count().next());
+ }
+
+ @Test
+ public void
shouldNotAllowDirtyReadsOfVertexPropertyUpdateForReadOnlyTransaction() throws
InterruptedException {
+ final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+ final GraphTraversalSource gtx = g.tx().begin();
+
+ gtx.addV().property("a", 1L).iterate();
+ gtx.tx().commit();
+
+ gtx.V().property("a", 2L).iterate();
+
+ final AtomicLong gtx2Read = new AtomicLong(-1);
+ final Thread thread = new Thread(() -> {
+ final GraphTraversalSource gtx2 = g.tx().begin();
+
+ gtx2Read.set((long) gtx2.V().values("a").next());
+ });
+ thread.start();
+ thread.join();
+
+ assertEquals(1, gtx2Read.get());
+ assertEquals(2, (long) gtx.V().values("a").next());
+ }
+
+ @Test
+ public void
shouldNotAllowDirtyReadsOfEdgePropertyAddForReadOnlyTransaction() throws
InterruptedException {
+ final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+ final GraphTraversalSource gtx = g.tx().begin();
+
+ final Vertex v1 = gtx.addV().next();
+ final Vertex v2 = gtx.addV().next();
+ gtx.addE("test").from(v1).to(v2).iterate();
+ gtx.tx().commit();
+
+ gtx.E().property("a", 1).iterate();
+
+ final AtomicLong gtx2Read = new AtomicLong(-1);
+ final Thread thread = new Thread(() -> {
+ final GraphTraversalSource gtx2 = g.tx().begin();
+
+ gtx2Read.set(gtx2.E().properties().count().next());
+ });
+ thread.start();
+ thread.join();
+
+ assertEquals(0, gtx2Read.get());
+ assertEquals(1, (long) gtx.E().properties().count().next());
+ }
+
+ @Test
+ public void
shouldNotAllowDirtyReadsOfEdgePropertyDropForReadOnlyTransaction() throws
InterruptedException {
+ final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+ final GraphTraversalSource gtx = g.tx().begin();
+
+ final Vertex v1 = gtx.addV().next();
+ final Vertex v2 = gtx.addV().next();
+ gtx.addE("test").from(v1).to(v2).property("a", 1).iterate();
+ gtx.tx().commit();
+
+ gtx.E().properties().drop().iterate();
+
+ final AtomicLong gtx2Read = new AtomicLong(-1);
+ final Thread thread = new Thread(() -> {
+ final GraphTraversalSource gtx2 = g.tx().begin();
+
+ gtx2Read.set(gtx2.E().properties().count().next());
+ });
+ thread.start();
+ thread.join();
+
+ assertEquals(1, gtx2Read.get());
+ assertEquals(0, (long) gtx.E().properties().count().next());
+ }
+
+ @Test
+ public void
shouldNotAllowDirtyReadsOfEdgePropertyUpdateForReadOnlyTransaction() throws
InterruptedException {
+ final TinkerTransactionGraph g = TinkerTransactionGraph.open();
+ final GraphTraversalSource gtx = g.tx().begin();
+
+ final Vertex v1 = gtx.addV().next();
+ final Vertex v2 = gtx.addV().next();
+ gtx.addE("test").from(v1).to(v2).property("a", 1L).iterate();
+ gtx.tx().commit();
+
+ gtx.E().property("a", 2L).iterate();
+
+ final AtomicLong gtx2Read = new AtomicLong(-1);
+ final Thread thread = new Thread(() -> {
+ final GraphTraversalSource gtx2 = g.tx().begin();
+
+ gtx2Read.set((long) gtx2.E().values("a").next());
+ });
+ thread.start();
+ thread.join();
+
+ assertEquals(1, gtx2Read.get());
+ assertEquals(2, (long) gtx.E().values("a").next());
}
}