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 e3215405ee56f2f0c80a086f6c6e2c8d0e42d064 Author: Ken Hu <[email protected]> AuthorDate: Thu Jul 27 09:11:27 2023 -0700 add tests --- .../structure/TransactionMultiThreadedTest.java | 30 ++ .../structure/TinkerTransactionGraphTest.java | 343 +++++++++++++++++++++ 2 files changed, 373 insertions(+) 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..ef3e3f1e00 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 @@ -526,6 +526,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) 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..824abe796e 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 @@ -18,7 +18,9 @@ */ package org.apache.tinkerpop.gremlin.tinkergraph.structure; +import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.T; import org.apache.tinkerpop.gremlin.structure.Vertex; @@ -26,9 +28,13 @@ import org.apache.tinkerpop.gremlin.structure.util.TransactionException; import org.junit.Test; import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -731,4 +737,341 @@ public class TinkerTransactionGraphTest { thread.start(); thread.join(); } + + @Test + public void shouldNotAllowDirtyReadsOfVertexForReadOnlyTransaction() throws InterruptedException { + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); + final GraphTraversalSource gtx = g.tx().begin(); + + gtx.addV().next(); + + AtomicLong gtx2Read = new AtomicLong(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2Read.set((long) gtx2.V().count().next()); + }); + thread.start(); + thread.join(); + assertEquals(0, gtx2Read.get()); + } + + @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(); + + AtomicLong gtx2Read = new AtomicLong(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2Read.set((long) gtx2.V().count().next()); + }); + thread.start(); + thread.join(); + assertEquals(1, gtx2Read.get()); + } + + @Test + public void shouldNotAllowDirtyReadsOfVertexPropertyForReadOnlyTransaction() 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(); + + AtomicLong gtx2Read = new AtomicLong(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2Read.set((long) gtx2.V().properties().count().next()); + }); + thread.start(); + thread.join(); + assertEquals(1, gtx2Read.get()); + } + + @Test + public void shouldNotAllowNonRepeatableReadsAfterDeletingVertexForReadOnlyTransaction() throws InterruptedException { + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); + final GraphTraversalSource gtx = g.tx().begin(); + + gtx.addV().iterate(); + gtx.tx().commit(); + + long firstRead = gtx.V().count().next(); + + AtomicLong gtx2Read = new AtomicLong(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2.V().drop().iterate(); + gtx2.tx().commit(); + + gtx2Read.set((long) gtx2.V().count().next()); + }); + thread.start(); + thread.join(); + assertEquals(0, gtx2Read.get()); + + long secondRead = gtx.V().count().next(); + assertEquals(firstRead, secondRead); + } + + @Test + public void shouldNotAllowNonRepeatableReadsAfterUpdatingVertexPropertyForReadOnlyTransaction() throws InterruptedException { + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); + final GraphTraversalSource gtx = g.tx().begin(); + + gtx.addV("test").property("a", 1).iterate(); + gtx.tx().commit(); + + Object firstRead = gtx.V().values("a").next(); + + AtomicLong gtx2Read = new AtomicLong(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2.V().property("a", 2L).iterate(); + gtx2.tx().commit(); + + gtx2Read.set((long) gtx2.V().values("a").next()); + }); + thread.start(); + thread.join(); + assertEquals(2, gtx2Read.get()); + + Object secondRead = gtx.V().values("a").next(); + assertEquals(firstRead, secondRead); + } + + @Test + public void shouldNotAllowNonRepeatableReadsAfterDeletingVertexPropertyForReadOnlyTransaction() throws InterruptedException { + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); + final GraphTraversalSource gtx = g.tx().begin(); + + gtx.addV("test").property("a", 1).iterate(); + gtx.tx().commit(); + + long firstReadCount = gtx.V().properties().count().next(); + + AtomicLong gtx2Count = new AtomicLong(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2.V().properties().drop().iterate(); + gtx2.tx().commit(); + + gtx2Count.set(gtx2.V().properties().count().next()); + }); + thread.start(); + thread.join(); + assertEquals(0, gtx2Count.get()); + + long secondReadCount = gtx.V().properties().count().next(); + assertEquals(firstReadCount, secondReadCount); + } + + @Test + public void shouldNotAllowNonRepeatableReadsAfterUpdatingEdgePropertyForReadOnlyTransaction() throws InterruptedException { + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); + final GraphTraversalSource gtx = g.tx().begin(); + + Vertex v1 = gtx.addV().next(); + Vertex v2 = gtx.addV().next(); + gtx.addE("test").from(v1).to(v2).property("a", 1).iterate(); + gtx.tx().commit(); + + Object firstReadCount = gtx.E().values("a").next(); + + AtomicInteger gtx2Count = new AtomicInteger(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2.E().property("a", 2).iterate(); + gtx2.tx().commit(); + + gtx2Count.set((int) gtx2.E().values("a").next()); + }); + thread.start(); + thread.join(); + assertEquals(2, gtx2Count.get()); + + Object secondReadCount = gtx.E().values("a").next(); + assertEquals(firstReadCount, secondReadCount); + } + + @Test + public void shouldNotAllowNonRepeatableReadsAfterDeletingEdgePropertyForReadOnlyTransaction() throws InterruptedException { + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); + final GraphTraversalSource gtx = g.tx().begin(); + + Vertex v1 = gtx.addV().next(); + Vertex v2 = gtx.addV().next(); + gtx.addE("test").from(v1).to(v2).property("a", 1).iterate(); + gtx.tx().commit(); + + long firstReadCount = gtx.E().properties().count().next(); + + AtomicLong gtx2Count = new AtomicLong(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2.E().properties().drop().iterate(); + gtx2.tx().commit(); + + gtx2Count.set((long) gtx2.E().properties().count().next()); + }); + thread.start(); + thread.join(); + assertEquals(0, gtx2Count.get()); + + long secondReadCount = gtx.E().properties().count().next(); + assertEquals(firstReadCount, secondReadCount); + } + @Test + public void shouldNotAllowNonRepeatableReadsAfterDeletingEdgeForReadOnlyTransaction() throws InterruptedException { + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); + final GraphTraversalSource gtx = g.tx().begin(); + + Vertex v1 = gtx.addV().next(); + Vertex v2 = gtx.addV().next(); + gtx.addE("test").from(v1).to(v2).iterate(); + gtx.tx().commit(); + + long firstReadCount = gtx.E().count().next(); + + AtomicLong gtx2Count = new AtomicLong(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2.E().drop().iterate(); + gtx2.tx().commit(); + + gtx2Count.set(gtx2.E().count().next()); + }); + thread.start(); + thread.join(); + assertEquals(0, gtx2Count.get()); + + long secondReadCount = gtx.E().count().next(); + assertEquals(firstReadCount, secondReadCount); + } + + @Test + public void shouldNotAllowPhantomReadsAfterAddingVertexForReadOnlyTransaction() throws InterruptedException { + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); + final GraphTraversalSource gtx = g.tx().begin(); + + gtx.addV().iterate(); + gtx.addV().iterate(); + gtx.tx().commit(); + + long firstReadCount = gtx.V().count().next(); + + AtomicLong gtx2Count = new AtomicLong(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2.addV().iterate(); + gtx2.tx().commit(); + + gtx2Count.set(gtx2.V().count().next()); + }); + thread.start(); + thread.join(); + assertEquals(3, gtx2Count.get()); + + long secondReadCount = gtx.V().count().next(); + assertEquals(firstReadCount, secondReadCount); + } + + @Test + public void shouldNotAllowPhantomReadsAfterAddingEdgeForReadOnlyTransaction() throws InterruptedException { + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); + final GraphTraversalSource gtx = g.tx().begin(); + + Vertex v1 = gtx.addV().next(); + Vertex v2 = gtx.addV().next(); + gtx.addE("test").from(v1).to(v2).id().iterate(); + gtx.tx().commit(); + + long firstReadCount = gtx.E().count().next(); + + AtomicLong gtx2Count = new AtomicLong(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2.addE("test").from(v1).to(v1).iterate(); + gtx2.tx().commit(); + + gtx2Count.set(gtx2.E().count().next()); + }); + thread.start(); + thread.join(); + assertEquals(2, gtx2Count.get()); + + long secondReadCount = gtx.E().count().next(); + assertEquals(firstReadCount, secondReadCount); + } + + @Test + public void shouldNotAllowPhantomReadsAfterAddingPropertyForReadOnlyTransaction() throws InterruptedException { + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); + final GraphTraversalSource gtx = g.tx().begin(); + + Object vId = gtx.addV().property("a", 1).id().next(); + gtx.tx().commit(); + + long firstReadCount = gtx.V(vId).properties().count().next(); + + AtomicLong gtx2Count = new AtomicLong(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2.V(vId).property("b", 2).iterate(); + gtx2.tx().commit(); + + gtx2Count.set(gtx2.V(vId).properties().count().next()); + }); + thread.start(); + thread.join(); + assertEquals(2, gtx2Count.get()); + + long secondReadCount = gtx.V(vId).properties().count().next(); + assertEquals(firstReadCount, secondReadCount); + } + + /** + * Test the case to see whether a commit that adds a vertex in another transaction will be read by a transaction + * that is already opened but hasn't yet started to read. This can technically be considered as a type of phantom + * read although in most scenarios it doesn't matter. Still, we should maintain consistency for this. + */ + @Test + public void shouldReadCommittedWritesBeforeFirstReadForReadOnlyTransaction() throws InterruptedException { + final TinkerTransactionGraph g = TinkerTransactionGraph.open(); + final GraphTraversalSource gtx = g.tx().begin(); + + AtomicLong gtx2Count = new AtomicLong(-1); + final Thread thread = new Thread(() -> { + final GraphTraversalSource gtx2 = g.tx().begin(); + + gtx2.addV().iterate(); + gtx2.tx().commit(); + + gtx2Count.set(gtx2.V().count().next()); + }); + thread.start(); + thread.join(); + assertEquals(1, gtx2Count.get()); + + assertEquals(1, (long) gtx.V().count().next()); + } }
