This is an automated email from the ASF dual-hosted git repository.

kenhuuu pushed a commit to branch ken/txtests
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 9a9b0a8db455e3491f8a06ca37c993fb0865b967
Author: Ken Hu <[email protected]>
AuthorDate: Thu Jul 27 09:11:27 2023 -0700

    Add extra testing around transaction isolation levels and update 
documentation CTR.
---
 .../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());
     }
 }

Reply via email to