All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tinkerpop.gremlin.structure.TransactionTest Maven / Gradle / Ivy

package com.tinkerpop.gremlin.structure;

import com.tinkerpop.gremlin.AbstractGremlinSuite;
import com.tinkerpop.gremlin.AbstractGremlinTest;
import com.tinkerpop.gremlin.util.function.FunctionUtils;
import org.apache.commons.configuration.Configuration;
import org.junit.Test;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import static com.tinkerpop.gremlin.structure.Graph.Features.EdgePropertyFeatures;
import static com.tinkerpop.gremlin.structure.Graph.Features.VertexPropertyFeatures.*;
import static org.junit.Assert.*;

/**
 * @author Stephen Mallette (http://stephen.genoprime.com)
 */
public class TransactionTest extends AbstractGremlinTest {
    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    public void shouldAllowAutoTransactionToWorkWithoutMutationByDefault() {
        // expecting no exceptions to be thrown here
        g.tx().commit();
        g.tx().rollback();
        g.tx().commit();
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    public void shouldCommitElementAutoTransactionByDefault() {
        final Vertex v1 = g.addVertex();
        final Edge e1 = v1.addEdge("l", v1);
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 1);
        assertEquals(v1.id(), g.v(v1.id()).id());
        assertEquals(e1.id(), g.e(e1.id()).id());
        g.tx().commit();
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 1);
        assertEquals(v1.id(), g.v(v1.id()).id());
        assertEquals(e1.id(), g.e(e1.id()).id());

        g.v(v1.id()).remove();
        AbstractGremlinSuite.assertVertexEdgeCounts(0, 0);
        g.tx().rollback();
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 1);

        g.v(v1.id()).remove();
        AbstractGremlinSuite.assertVertexEdgeCounts(0, 0);
        g.tx().commit();
        AbstractGremlinSuite.assertVertexEdgeCounts(0, 0);
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    public void shouldRollbackElementAutoTransactionByDefault() {
        final Vertex v1 = g.addVertex();
        final Edge e1 = v1.addEdge("l", v1);
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 1);
        assertEquals(v1.id(), g.v(v1.id()).id());
        assertEquals(e1.id(), g.e(e1.id()).id());
        g.tx().rollback();
        AbstractGremlinSuite.assertVertexEdgeCounts(0, 0);
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    @FeatureRequirement(featureClass = Graph.Features.EdgePropertyFeatures.class, feature = EdgePropertyFeatures.FEATURE_STRING_VALUES)
    @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = FEATURE_STRING_VALUES)
    public void shouldCommitPropertyAutoTransactionByDefault() {
        final Vertex v1 = g.addVertex();
        final Edge e1 = v1.addEdge("l", v1);
        g.tx().commit();
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 1);
        assertEquals(v1.id(), g.v(v1.id()).id());
        assertEquals(e1.id(), g.e(e1.id()).id());

        v1.property("name", "marko");
        assertEquals("marko", v1.value("name"));
        assertEquals("marko", g.v(v1.id()).value("name"));
        g.tx().commit();

        assertEquals("marko", v1.value("name"));
        assertEquals("marko", g.v(v1.id()).value("name"));

        v1.property("name", "stephen");

        assertEquals("stephen", v1.value("name"));
        assertEquals("stephen", g.v(v1.id()).value("name"));

        g.tx().commit();

        assertEquals("stephen", v1.value("name"));
        assertEquals("stephen", g.v(v1.id()).value("name"));

        e1.property("name", "xxx");

        assertEquals("xxx", e1.value("name"));
        assertEquals("xxx", g.e(e1.id()).value("name"));

        g.tx().commit();

        assertEquals("xxx", e1.value("name"));
        assertEquals("xxx", g.e(e1.id()).value("name"));

        AbstractGremlinSuite.assertVertexEdgeCounts(1, 1);
        assertEquals(v1.id(), g.v(v1.id()).id());
        assertEquals(e1.id(), g.e(e1.id()).id());
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    @FeatureRequirement(featureClass = Graph.Features.EdgePropertyFeatures.class, feature = EdgePropertyFeatures.FEATURE_STRING_VALUES)
    @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = FEATURE_STRING_VALUES)
    public void shouldRollbackPropertyAutoTransactionByDefault() {
        final Vertex v1 = g.addVertex("name", "marko");
        final Edge e1 = v1.addEdge("l", v1, "name", "xxx");
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 1);
        assertEquals(v1.id(), g.v(v1.id()).id());
        assertEquals(e1.id(), g.e(e1.id()).id());
        assertEquals("marko", v1.value("name"));
        assertEquals("xxx", e1.value("name"));
        g.tx().commit();

        assertEquals("marko", v1.value("name"));
        assertEquals("marko", g.v(v1.id()).value("name"));

        v1.property("name", "stephen");

        assertEquals("stephen", v1.value("name"));
        assertEquals("stephen", g.v(v1.id()).value("name"));

        g.tx().rollback();

        assertEquals("marko", v1.value("name"));
        assertEquals("marko", g.v(v1.id()).value("name"));

        e1.property("name", "yyy");

        assertEquals("yyy", e1.value("name"));
        assertEquals("yyy", g.e(e1.id()).value("name"));

        g.tx().rollback();

        assertEquals("xxx", e1.value("name"));
        assertEquals("xxx", g.e(e1.id()).value("name"));

        AbstractGremlinSuite.assertVertexEdgeCounts(1, 1);
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = FEATURE_STRING_VALUES)
    public void shouldCommitOnShutdownByDefault() throws Exception {
        final Vertex v1 = g.addVertex("name", "marko");
        final Object oid = v1.id();
        g.close();

        g = graphProvider.openTestGraph(config);
        final Vertex v2 = g.v(oid);
        assertEquals("marko", v2.value("name"));
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = FEATURE_STRING_VALUES)
    public void shouldRollbackOnShutdownWhenConfigured() throws Exception {
        final Vertex v1 = g.addVertex("name", "marko");
        final Object oid = v1.id();
        g.tx().onClose(Transaction.CLOSE_BEHAVIOR.ROLLBACK);
        g.close();

        g = graphProvider.openTestGraph(config);
        try {
            g.v(oid);
            fail("Vertex should not be found as close behavior was set to rollback");
        } catch (Exception ex) {
            final Exception expected = Graph.Exceptions.elementNotFound(Vertex.class, oid);
            assertEquals(expected.getMessage(), ex.getMessage());
        }
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = FEATURE_FLOAT_VALUES)
    @FeatureRequirement(featureClass = Graph.Features.VertexPropertyFeatures.class, feature = FEATURE_INTEGER_VALUES)
    @FeatureRequirement(featureClass = Graph.Features.EdgePropertyFeatures.class, feature = EdgePropertyFeatures.FEATURE_FLOAT_VALUES)
    @FeatureRequirement(featureClass = Graph.Features.EdgePropertyFeatures.class, feature = EdgePropertyFeatures.FEATURE_INTEGER_VALUES)
    public void shouldExecuteWithCompetingThreads() {
        final Graph graph = g;
        int totalThreads = 250;
        final AtomicInteger vertices = new AtomicInteger(0);
        final AtomicInteger edges = new AtomicInteger(0);
        final AtomicInteger completedThreads = new AtomicInteger(0);
        for (int i = 0; i < totalThreads; i++) {
            new Thread() {
                public void run() {
                    final Random random = new Random();
                    if (random.nextBoolean()) {
                        final Vertex a = graph.addVertex();
                        final Vertex b = graph.addVertex();
                        final Edge e = a.addEdge("friend", b);

                        vertices.getAndAdd(2);
                        a.property("test", this.getId());
                        b.property("blah", random.nextFloat());
                        e.property("bloop", random.nextInt());
                        edges.getAndAdd(1);
                        graph.tx().commit();
                    } else {
                        final Vertex a = graph.addVertex();
                        final Vertex b = graph.addVertex();
                        final Edge e = a.addEdge("friend", b);

                        a.property("test", this.getId());
                        b.property("blah", random.nextFloat());
                        e.property("bloop", random.nextInt());

                        if (random.nextBoolean()) {
                            graph.tx().commit();
                            vertices.getAndAdd(2);
                            edges.getAndAdd(1);
                        } else {
                            graph.tx().rollback();
                        }
                    }
                    completedThreads.getAndAdd(1);
                }
            }.start();
        }

        while (completedThreads.get() < totalThreads) {
        }

        assertEquals(completedThreads.get(), 250);
        AbstractGremlinSuite.assertVertexEdgeCounts(vertices.get(), edges.get());
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    public void shouldExcecuteCompetingThreadsOnMultipleDbInstances() throws Exception {
        // the idea behind this test is to simulate a gremlin-server environment where two graphs of the same type
        // are being mutated by multiple threads. originally replicated a bug that was part of OrientDB.

        final Configuration configuration = graphProvider.newGraphConfiguration("g1");
        graphProvider.clear(configuration);
        final Graph g1 = graphProvider.openTestGraph(configuration);

        final Thread threadModFirstGraph = new Thread() {
            public void run() {
                g.addVertex();
                g.tx().commit();
            }
        };

        threadModFirstGraph.start();
        threadModFirstGraph.join();

        final Thread threadReadBothGraphs = new Thread() {
            public void run() {
                final long gCounter = g.V().count().next();
                assertEquals(1l, gCounter);

                final long g1Counter = g1.V().count().next();
                assertEquals(0l, g1Counter);
            }
        };

        threadReadBothGraphs.start();
        threadReadBothGraphs.join();

        // need to manually close the "g1" instance
        graphProvider.clear(g1, configuration);
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    public void shouldSupportTransactionIsolationCommitCheck() throws Exception {
        // the purpose of this test is to simulate gremlin server access to a graph instance, where one thread modifies
        // the graph and a separate thread cannot affect the transaction of the first
        final Graph graph = g;

        final CountDownLatch latchCommittedInOtherThread = new CountDownLatch(1);
        final CountDownLatch latchCommitInOtherThread = new CountDownLatch(1);

        final AtomicBoolean noVerticesInFirstThread = new AtomicBoolean(false);

        // this thread starts a transaction then waits while the second thread tries to commit it.
        final Thread threadTxStarter = new Thread() {
            public void run() {
                graph.addVertex();
                latchCommitInOtherThread.countDown();

                try {
                    latchCommittedInOtherThread.await();
                } catch (InterruptedException ie) {
                    throw new RuntimeException(ie);
                }

                graph.tx().rollback();

                // there should be no vertices here
                noVerticesInFirstThread.set(!graph.V().hasNext());
            }
        };

        threadTxStarter.start();

        // this thread tries to commit the transaction started in the first thread above.
        final Thread threadTryCommitTx = new Thread() {
            public void run() {
                try {
                    latchCommitInOtherThread.await();
                } catch (InterruptedException ie) {
                    throw new RuntimeException(ie);
                }

                // try to commit the other transaction
                graph.tx().commit();

                latchCommittedInOtherThread.countDown();
            }
        };

        threadTryCommitTx.start();

        threadTxStarter.join();
        threadTryCommitTx.join();

        assertTrue(noVerticesInFirstThread.get());
        AbstractGremlinSuite.assertVertexEdgeCounts(0, 0);
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    public void shouldSupportTransactionFireAndForget() {
        final Graph graph = g;

        // first fail the tx
        g.tx().submit(FunctionUtils.wrapFunction(grx -> {
            grx.addVertex();
            throw new Exception("fail");
        })).fireAndForget();
        AbstractGremlinSuite.assertVertexEdgeCounts(0, 0);

        // this tx will work
        g.tx().submit(grx -> graph.addVertex()).fireAndForget();
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 0);

        // make sure a commit happened and a new tx started
        g.tx().rollback();
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 0);
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    public void shouldSupportTransactionOneAndDone() {
        final Graph graph = g;

        // first fail the tx
        try {
            g.tx().submit(FunctionUtils.wrapFunction(grx -> {
                grx.addVertex();
                throw new Exception("fail");
            })).oneAndDone();
        } catch (Exception ex) {
            assertEquals("fail", ex.getCause().getCause().getMessage());
        }

        AbstractGremlinSuite.assertVertexEdgeCounts(0, 0);

        // this tx will work
        g.tx().submit(grx -> graph.addVertex()).oneAndDone();
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 0);

        // make sure a commit happened and a new tx started
        g.tx().rollback();
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 0);
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    public void shouldSupportTransactionExponentialBackoff() {
        final Graph graph = g;

        // first fail the tx
        final AtomicInteger attempts = new AtomicInteger(0);
        try {
            g.tx().submit(FunctionUtils.wrapFunction(grx -> {
                grx.addVertex();
                attempts.incrementAndGet();
                throw new Exception("fail");
            })).exponentialBackoff();
        } catch (Exception ex) {
            assertEquals("fail", ex.getCause().getCause().getMessage());
        }

        assertEquals(Transaction.Workload.DEFAULT_TRIES, attempts.get());
        AbstractGremlinSuite.assertVertexEdgeCounts(0, 0);

        // this tx will work after several tries
        final AtomicInteger tries = new AtomicInteger(0);
        g.tx().submit(FunctionUtils.wrapFunction(grx -> {
            final int tryNumber = tries.incrementAndGet();
            if (tryNumber == Transaction.Workload.DEFAULT_TRIES - 2)
                return graph.addVertex();
            else
                throw new Exception("fail");
        })).exponentialBackoff();

        assertEquals(Transaction.Workload.DEFAULT_TRIES - 2, tries.get());
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 0);

        // make sure a commit happened and a new tx started
        g.tx().rollback();
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 0);
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    public void shouldSupportTransactionExponentialBackoffWithExceptionChecks() {
        final Graph graph = g;
        final Set exceptions = new HashSet() {{
            add(IllegalStateException.class);
        }};

        // first fail the tx
        final AtomicInteger attempts = new AtomicInteger(0);
        try {
            g.tx().submit(FunctionUtils.wrapFunction(grx -> {
                grx.addVertex();
                attempts.incrementAndGet();
                throw new Exception("fail");
            })).exponentialBackoff(Transaction.Workload.DEFAULT_TRIES, 20, exceptions);
        } catch (Exception ex) {
            assertEquals("fail", ex.getCause().getCause().getMessage());
        }

        assertEquals(1, attempts.get());
        AbstractGremlinSuite.assertVertexEdgeCounts(0, 0);

        // this tx will retry for specific tx and then fail after several tries
        final AtomicInteger setOfTries = new AtomicInteger(0);
        try {
            g.tx().submit(FunctionUtils.wrapFunction(grx -> {
                final int tryNumber = setOfTries.incrementAndGet();
                if (tryNumber == Transaction.Workload.DEFAULT_TRIES - 2)
                    throw new Exception("fail");
                else
                    throw new IllegalStateException("fail");
            })).exponentialBackoff(Transaction.Workload.DEFAULT_TRIES, 20, exceptions);
        } catch (Exception ex) {
            assertEquals("fail", ex.getCause().getCause().getMessage());
        }

        assertEquals(Transaction.Workload.DEFAULT_TRIES - 2, setOfTries.get());
        AbstractGremlinSuite.assertVertexEdgeCounts(0, 0);

        // this tx will work after several tries
        final AtomicInteger tries = new AtomicInteger(0);
        g.tx().submit(FunctionUtils.wrapFunction(grx -> {
            final int tryNumber = tries.incrementAndGet();
            if (tryNumber == Transaction.Workload.DEFAULT_TRIES - 2)
                return graph.addVertex();
            else
                throw new IllegalStateException("fail");
        })).exponentialBackoff(Transaction.Workload.DEFAULT_TRIES, 20, exceptions);

        assertEquals(Transaction.Workload.DEFAULT_TRIES - 2, tries.get());
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 0);

        // make sure a commit happened and a new tx started
        g.tx().rollback();
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 0);
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    public void shouldSupportTransactionRetry() {
        final Graph graph = g;

        // first fail the tx
        final AtomicInteger attempts = new AtomicInteger(0);
        try {
            g.tx().submit(FunctionUtils.wrapFunction(grx -> {
                grx.addVertex();
                attempts.incrementAndGet();
                throw new Exception("fail");
            })).retry();
        } catch (Exception ex) {
            assertEquals("fail", ex.getCause().getCause().getMessage());
        }

        assertEquals(Transaction.Workload.DEFAULT_TRIES, attempts.get());
        AbstractGremlinSuite.assertVertexEdgeCounts(0, 0);

        // this tx will work after several tries
        final AtomicInteger tries = new AtomicInteger(0);
        g.tx().submit(FunctionUtils.wrapFunction(grx -> {
            final int tryNumber = tries.incrementAndGet();
            if (tryNumber == Transaction.Workload.DEFAULT_TRIES - 2)
                return graph.addVertex();
            else
                throw new Exception("fail");
        })).retry();

        assertEquals(Transaction.Workload.DEFAULT_TRIES - 2, tries.get());
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 0);

        // make sure a commit happened and a new tx started
        g.tx().rollback();
        AbstractGremlinSuite.assertVertexEdgeCounts(1, 0);
    }

    @Test
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_TRANSACTIONS)
    @FeatureRequirement(featureClass = Graph.Features.GraphFeatures.class, feature = Graph.Features.GraphFeatures.FEATURE_FULLY_ISOLATED_TRANSACTIONS)
    public void shouldSupportTransactionIsolationWithSeparateThreads() throws Exception {
        // one thread modifies the graph and a separate thread reads before the transaction is committed.
        // the expectation is that the changes in the transaction are isolated to the thread that made the change
        // and the second thread should not see the change until commit() in the first thread.
        final Graph graph = g;

        final CountDownLatch latchCommit = new CountDownLatch(1);
        final CountDownLatch latchFirstRead = new CountDownLatch(1);
        final CountDownLatch latchSecondRead = new CountDownLatch(1);

        final Thread threadMod = new Thread() {
            public void run() {
                graph.addVertex();

                latchFirstRead.countDown();

                try {
                    latchCommit.await();
                } catch (InterruptedException ie) {
                    throw new RuntimeException(ie);
                }

                graph.tx().commit();

                latchSecondRead.countDown();
            }
        };

        threadMod.start();

        final AtomicLong beforeCommitInOtherThread = new AtomicLong(0);
        final AtomicLong afterCommitInOtherThread = new AtomicLong(0);
        final Thread threadRead = new Thread() {
            public void run() {
                try {
                    latchFirstRead.await();
                } catch (InterruptedException ie) {
                    throw new RuntimeException(ie);
                }

                // reading vertex before tx from other thread is committed...should have zero vertices
                beforeCommitInOtherThread.set(graph.V().count().next());

                latchCommit.countDown();

                try {
                    latchSecondRead.await();
                } catch (InterruptedException ie) {
                    throw new RuntimeException(ie);
                }

                // tx in other thread is committed...should have one vertex.  rollback first to start a new tx
                // to get a fresh read given the commit
                graph.tx().rollback();
                afterCommitInOtherThread.set(graph.V().count().next());
            }
        };

        threadRead.start();

        threadMod.join();
        threadRead.join();

        assertEquals(0l, beforeCommitInOtherThread.get());
        assertEquals(1l, afterCommitInOtherThread.get());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy