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