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

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

package com.tinkerpop.gremlin.structure;

import java.io.Closeable;
import java.util.Collections;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author Marko A. Rodriguez (http://markorodriguez.com)
 * @author Stephen Mallette (http://stephen.genoprime.com)
 * @author TinkerPop Community (http://tinkerpop.com)
 */
public interface Transaction extends Closeable {

    /**
     * Opens a transaction.
     */
    public void open();

    /**
     * Commits a transaction.
     */
    public void commit();

    /**
     * Rolls back a transaction.
     */
    public void rollback();

    /**
     * Submit a unit of work that represents a transaction returning a {@link Workload} that can be automatically
     * retried in the event of failure.
     */
    public  Workload submit(final Function work);

    /**
     * Creaes a transaction that can be executed across multiple threads.
     */
    public  G create();

    /**
     * Determines if a transaction is currently open.
     */
    public boolean isOpen();

    /**
     * An internal function that signals a read or a write has occurred - not meant to be called directly.
     */
    public void readWrite();

    /**
     * Closes the transaction where the default close behavior will be executed.
     */
    public void close();

    /**
     * Describes how a transaction is started when a read or a write occurs.  This value can be set using standard
     * behaviors defined in {@link READ_WRITE_BEHAVIOR} or a custom {@link Consumer} function.
     */
    public Transaction onReadWrite(final Consumer consumer);

    /**
     * Describes what happens to a transaction on a call to {@link com.tinkerpop.gremlin.structure.Graph#close()}.
     * This value can be set using standard behavior defined in {@link CLOSE_BEHAVIOR} or a custom {@link Consumer}
     * function.
     */
    public Transaction onClose(final Consumer consumer);

    public static class Exceptions {
        public static IllegalStateException transactionAlreadyOpen() {
            return new IllegalStateException("Stop the current transaction before opening another");
        }

        public static IllegalStateException transactionMustBeOpenToReadWrite() {
            return new IllegalStateException("Open a transaction before attempting to read/write the transaction");
        }

        public static IllegalStateException openTransactionsOnClose() {
            return new IllegalStateException("Commit or rollback all outstanding transactions before closing the transaction");
        }

        public static UnsupportedOperationException threadedTransactionsNotSupported() {
            return new UnsupportedOperationException("Graph does not support threaded transactions");
        }

        public static IllegalArgumentException onCloseBehaviorCannotBeNull() {
            return new IllegalArgumentException("Transaction behavior for onClose cannot be null");
        }

        public static IllegalArgumentException onReadWriteBehaviorCannotBeNull() {
            return new IllegalArgumentException("Transaction behavior for onReadWrite cannot be null");
        }
    }

    /**
     * Behaviors to supply to the {@link #onClose(java.util.function.Consumer)}.
     */
    public static enum CLOSE_BEHAVIOR implements Consumer {
        /**
         * Any open transaction will commit on close.
         */
        COMMIT {
            @Override
            public void accept(final Transaction transaction) {
                if (transaction.isOpen()) transaction.commit();
            }
        },

        /**
         * Any open transaction will rollback on close.
         */
        ROLLBACK {
            @Override
            public void accept(final Transaction transaction) {
                if (transaction.isOpen()) transaction.rollback();
            }
        },

        /**
         * Open transactions on close will throw an exception
         */
        MANUAL {
            @Override
            public void accept(final Transaction transaction) {
                if (transaction.isOpen()) throw Exceptions.openTransactionsOnClose();
            }
        }
    }

    /**
     * Behaviors to supply to the {@link #onReadWrite(java.util.function.Consumer)}.
     */
    public static enum READ_WRITE_BEHAVIOR implements Consumer {
        /**
         * Transactions are automatically started when a read or a write occurs.
         */
        AUTO {
            @Override
            public void accept(final Transaction transaction) {
                if (!transaction.isOpen()) transaction.open();
            }
        },

        /**
         * Transactions must be explicitly opened for operations to occur on the graph.
         */
        MANUAL {
            @Override
            public void accept(final Transaction transaction) {
                if (!transaction.isOpen()) throw Exceptions.transactionMustBeOpenToReadWrite();
            }
        }
    }

    /**
     * A {@link Workload} represents a unit of work constructed by the
     * {@link Workload#submit(java.util.function.Function)} method on the {@link Transaction} interface.
     * The unit of work is a {@link java.util.function.Function} typically containing mutations to the {@link Graph}.  The
     * {@link Workload} is responsible for executing the unit of work in the context of a retry strategy, such that a
     * failed unit of work is rolled back and executed again until retries are exhausted or the unit of work succeeds
     * with a commit operation.
     *
     * @param  The type of the result from the unit of work.
     */
    public static class Workload {
        public static final long DEFAULT_DELAY_MS = 20;
        public static final int DEFAULT_TRIES = 8;

        private final Function workToDo;
        private final Graph g;

        /**
         * Creates a new {@link Workload} that will be tried to be executed within a transaction.
         *
         * @param g    The {@link Graph} instance on which the work will be performed.
         * @param work The work to be executed on the Graph instance which will optionally return a value.
         */
        public Workload(final Graph g, final Function work) {
            this.g = g;
            this.workToDo = work;
        }

        /**
         * Try to execute a {@link Workload} with a custom retry strategy.
         *
         * @param retryStrategy The first argument to this function is the Graph instance and the second is
         *                      the encapsulated work to be performed.  The function should ultimately return the
         *                      result of the encapsulated work function.
         * @return The result of the encapsulated work.
         */
        public R attempt(final BiFunction, R> retryStrategy) {
            return retryStrategy.apply(g, workToDo);
        }

        /**
         * Executes the {@link Workload} committing if possible and rolling back on failure.  On failure, an exception
         * is reported.
         */
        public R oneAndDone() {
            return attempt((g, w) -> {
                try {
                    R result = w.apply(g);
                    g.tx().commit();

                    return result;
                } catch (Throwable t) {
                    g.tx().rollback();
                    throw new RuntimeException(t);
                }
            });
        }

        /**
         * Executes the {@link Workload} committing if possible and rolling back on failure.  On failure no exception
         * is reported.
         */
        public R fireAndForget() {
            return attempt((g, w) -> {
                R result = null;
                try {
                    result = w.apply(g);
                    g.tx().commit();
                } catch (Throwable t) {
                    g.tx().rollback();
                }

                return result;
            });
        }

        /**
         * Executes the {@link Workload} with the default number of retries and with the default number of
         * milliseconds delay between each try.
         */
        public R retry() {
            return retry(DEFAULT_TRIES);
        }

        /**
         * Executes the {@link Workload} with a number of retries and with the default number of milliseconds delay
         * between each try.
         */
        public R retry(final int tries) {
            return retry(tries, DEFAULT_DELAY_MS);
        }

        /**
         * Executes the {@link Workload} with a number of retries and with a number of milliseconds delay between
         * each try.
         */
        public R retry(final int tries, final long delay) {
            return retry(tries, delay, Collections.emptySet());
        }

        /**
         * Executes the {@link Workload} with a number of retries and with a number of milliseconds delay between each
         * try and will only retry on the set of supplied exceptions.  Exceptions outside of that set will generate a
         * RuntimeException and immediately fail.
         */
        public R retry(final int tries, final long delay, final Set exceptionsToRetryOn) {
            return attempt(retry(tries, exceptionsToRetryOn, i -> delay));
        }

        /**
         * Executes the {@link Workload} with the default number of retries and with a exponentially increasing
         * number of milliseconds between each retry using the default retry delay.
         */
        public R exponentialBackoff() {
            return exponentialBackoff(DEFAULT_TRIES);
        }

        /**
         * Executes the {@link Workload} with a number of retries and with a exponentially increasing number of
         * milliseconds between each retry using the default retry delay.
         */
        public R exponentialBackoff(final int tries) {
            return exponentialBackoff(tries, DEFAULT_DELAY_MS);
        }

        /**
         * Executes the {@link Workload} with a number of retries and with a exponentially increasing number of
         * milliseconds between each retry.
         */
        public R exponentialBackoff(final int tries, final long initialDelay) {
            return exponentialBackoff(tries, initialDelay, Collections.emptySet());
        }

        /**
         * Executes the {@link Workload} with a number of retries and with a exponentially increasing number of
         * milliseconds between each retry.  It will only retry on the set of supplied exceptions.  Exceptions outside
         * of that set will generate a {@link RuntimeException} and immediately fail.
         */
        public R exponentialBackoff(final int tries, final long initialDelay, final Set exceptionsToRetryOn) {
            return attempt(retry(tries, exceptionsToRetryOn, retryCount -> (long) (initialDelay * Math.pow(2, retryCount))));
        }

        /**
         * Creates a generic retry function to be passed to the {@link Workload#attempt(java.util.function.BiFunction)}
         * method.
         */
        private static  BiFunction, R> retry(final int tries,
                                                                          final Set exceptionsToRetryOn,
                                                                          final Function delay) {
            return (g, w) -> {
                R returnValue;

                // this is the default exception...it may get reassgined during retries
                Exception previousException = new RuntimeException("Exception initialized when trying commit");

                // try to commit a few times
                for (int ix = 0; ix < tries; ix++) {

                    // increase time after each failed attempt though there is no delay on the first try
                    if (ix > 0)
                        try {
                            Thread.sleep(delay.apply(ix));
                        } catch (InterruptedException ignored) {
                        }

                    try {
                        returnValue = w.apply(g);
                        g.tx().commit();

                        // need to exit the function here so that retries don't happen
                        return returnValue;
                    } catch (Exception ex) {
                        g.tx().rollback();

                        // retry if this is an allowed exception otherwise, just throw and go
                        boolean retry = false;
                        if (exceptionsToRetryOn.size() == 0)
                            retry = true;
                        else {
                            for (Class exceptionToRetryOn : exceptionsToRetryOn) {
                                if (ex.getClass().equals(exceptionToRetryOn)) {
                                    retry = true;
                                    break;
                                }
                            }
                        }

                        if (!retry) {
                            throw new RuntimeException(ex);
                        }

                        previousException = ex;
                    }
                }

                // the exception just won't go away after all the retries
                throw new RuntimeException(previousException);
            };
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy