com.sleepycat.collections.TransactionRunner Maven / Gradle / Ivy
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.collections;
import com.sleepycat.compat.DbCompat;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockConflictException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;
import com.sleepycat.util.ExceptionUnwrapper;
/**
* Starts a transaction, calls {@link TransactionWorker#doWork}, and handles
* transaction retry and exceptions. To perform a transaction, the user
* implements the {@link TransactionWorker} interface and passes an instance of
* that class to the {@link #run run} method.
*
* A single TransactionRunner instance may be used by any number of threads
* for any number of transactions.
*
* The behavior of the run() method depends on whether the environment is
* transactional, whether nested transactions are enabled, and whether a
* transaction is already active.
*
*
* - When the run() method is called in a transactional environment and no
* transaction is active for the current thread, a new transaction is started
* before calling doWork(). If LockConflictException is thrown by doWork(),
* the transaction will be aborted and the process will be repeated up to the
* maximum number of retries. If another exception is thrown by doWork() or
* the maximum number of retries has occurred, the transaction will be aborted
* and the exception will be rethrown by the run() method. If no exception is
* thrown by doWork(), the transaction will be committed. The run() method
* will not attempt to commit or abort a transaction if it has already been
* committed or aborted by doWork().
*
* - When the run() method is called and a transaction is active for the
* current thread, and nested transactions are enabled, a nested transaction is
* started before calling doWork(). The transaction that is active when
* calling the run() method will become the parent of the nested transaction.
* The nested transaction will be committed or aborted by the run() method
* following the same rules described above. Note that nested transactions may
* not be enabled for the JE product, since JE does not support nested
* transactions.
*
* - When the run() method is called in a non-transactional environment, the
* doWork() method is called without starting a transaction. The run() method
* will return without committing or aborting a transaction, and any exceptions
* thrown by the doWork() method will be thrown by the run() method.
*
* - When the run() method is called and a transaction is active for the
* current thread and nested transactions are not enabled (the default) the
* same rules as above apply. All the operations performed by the doWork()
* method will be part of the currently active transaction.
*
*
* In a transactional environment, the rules described above support nested
* calls to the run() method and guarantee that the outermost call will cause
* the transaction to be committed or aborted. This is true whether or not
* nested transactions are supported or enabled. Note that nested transactions
* are provided as an optimization for improving concurrency but do not change
* the meaning of the outermost transaction. Nested transactions are not
* currently supported by the JE product.
*
* @author Mark Hayes
*/
public class TransactionRunner {
/** The default maximum number of retries. */
public static final int DEFAULT_MAX_RETRIES = 10;
private CurrentTransaction currentTxn;
private int maxRetries;
private TransactionConfig config;
private boolean allowNestedTxn;
/**
* Creates a transaction runner for a given Berkeley DB environment.
* The default maximum number of retries ({@link #DEFAULT_MAX_RETRIES}) and
* a null (default) {@link TransactionConfig} will be used.
*
* @param env is the environment for running transactions.
*/
public TransactionRunner(Environment env) {
this(env, DEFAULT_MAX_RETRIES, null);
}
/**
* Creates a transaction runner for a given Berkeley DB environment and
* with a given number of maximum retries.
*
* @param env is the environment for running transactions.
*
* @param maxRetries is the maximum number of retries that will be
* performed when deadlocks are detected.
*
* @param config the transaction configuration used for calling
* {@link Environment#beginTransaction}, or null to use the default
* configuration. The configuration object is not cloned, and
* any modifications to it will impact subsequent transactions.
*/
public TransactionRunner(Environment env,
int maxRetries,
TransactionConfig config) {
this.currentTxn = CurrentTransaction.getInstance(env);
this.maxRetries = maxRetries;
this.config = config;
}
/**
* Returns the maximum number of retries that will be performed when
* deadlocks are detected.
*
* @return the maximum number of retries.
*/
public int getMaxRetries() {
return maxRetries;
}
/**
* Changes the maximum number of retries that will be performed when
* deadlocks are detected.
* Calling this method does not impact transactions already running.
*
* @param maxRetries the maximum number of retries.
*/
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
/**
* Returns whether nested transactions will be created if
* run()
is called when a transaction is already active for
* the current thread.
* By default this property is false.
*
* @return whether nested transactions will be created.
*
* Note that this method always returns false in the JE product, since
* nested transactions are not supported by JE.
*/
public boolean getAllowNestedTransactions() {
return allowNestedTxn;
}
/**
* Changes whether nested transactions will be created if
* run()
is called when a transaction is already active for
* the current thread.
* Calling this method does not impact transactions already running.
*
* @param allowNestedTxn whether nested transactions will be created.
*
* Note that true may not be passed to this method in the JE product,
* since nested transactions are not supported by JE.
*/
public void setAllowNestedTransactions(boolean allowNestedTxn) {
if (allowNestedTxn && !DbCompat.NESTED_TRANSACTIONS) {
throw new UnsupportedOperationException
("Nested transactions are not supported.");
}
this.allowNestedTxn = allowNestedTxn;
}
/**
* Returns the transaction configuration used for calling
* {@link Environment#beginTransaction}.
*
* If this property is null, the default configuration is used. The
* configuration object is not cloned, and any modifications to it will
* impact subsequent transactions.
*
* @return the transaction configuration.
*/
public TransactionConfig getTransactionConfig() {
return config;
}
/**
* Changes the transaction configuration used for calling
* {@link Environment#beginTransaction}.
*
* If this property is null, the default configuration is used. The
* configuration object is not cloned, and any modifications to it will
* impact subsequent transactions.
*
* @param config the transaction configuration.
*/
public void setTransactionConfig(TransactionConfig config) {
this.config = config;
}
/**
* Calls the {@link TransactionWorker#doWork} method and, for transactional
* environments, may begin and end a transaction. If the environment given
* is non-transactional, a transaction will not be used but the doWork()
* method will still be called. See the class description for more
* information.
*
* @param worker the TransactionWorker.
*
* @throws LockConflictException when it is thrown by doWork() and the
* maximum number of retries has occurred. The transaction will have been
* aborted by this method.
*
* @throws Exception when any other exception is thrown by doWork(). The
* exception will first be unwrapped by calling {@link
* ExceptionUnwrapper#unwrap}. The transaction will have been aborted by
* this method.
*/
public void run(TransactionWorker worker)
throws DatabaseException, Exception {
if (currentTxn != null &&
(allowNestedTxn || currentTxn.getTransaction() == null)) {
/* Transactional and (not nested or nested txns allowed). */
int useMaxRetries = maxRetries;
for (int retries = 0;; retries += 1) {
Transaction txn = null;
try {
txn = currentTxn.beginTransaction(config);
worker.doWork();
if (txn != null && txn == currentTxn.getTransaction()) {
currentTxn.commitTransaction();
}
return;
} catch (Throwable e) {
e = ExceptionUnwrapper.unwrapAny(e);
if (txn != null && txn == currentTxn.getTransaction()) {
try {
currentTxn.abortTransaction();
} catch (Throwable e2) {
/*
* We print this stack trace so that the
* information is not lost when we throw the
* original exception.
*/
if (DbCompat.
TRANSACTION_RUNNER_PRINT_STACK_TRACES) {
e2.printStackTrace();
}
/* Force the original exception to be thrown. */
retries = useMaxRetries;
}
}
/* An Error should not require special handling. */
if (e instanceof Error) {
throw (Error) e;
}
/* Allow a subclass to determine retry policy. */
Exception ex = (Exception) e;
useMaxRetries =
handleException(ex, retries, useMaxRetries);
if (retries >= useMaxRetries) {
throw ex;
}
}
}
} else {
/* Non-transactional or (nested and no nested txns allowed). */
try {
worker.doWork();
} catch (Exception e) {
throw ExceptionUnwrapper.unwrap(e);
}
}
}
/**
* Handles exceptions that occur during a transaction, and may implement
* transaction retry policy. The transaction is aborted by the {@link
* #run run} method before calling this method.
*
* The default implementation of this method throws the {@code
* exception} parameter if it is not an instance of {@link
* LockConflictException} and otherwise returns the {@code maxRetries}
* parameter value. This method can be overridden to throw a different
* exception or return a different number of retries. For example:
*
* - This method could call {@code Thread.sleep} for a short interval to
* allow other transactions to finish.
*
* - This method could return a different {@code maxRetries} value
* depending on the {@code exception} that occurred.
*
* - This method could throw an application-defined exception when the
* {@code retries} value is greater or equal to the {@code maxRetries} and
* a {@link LockConflictException} occurs, to override the default behavior
* which is to throw the {@link LockConflictException}.
*
*
* @param exception an exception that was thrown by the {@link
* TransactionWorker#doWork} method or thrown when beginning or committing
* the transaction. If the {@code retries} value is greater or equal to
* {@code maxRetries} when this method returns normally, this exception
* will be thrown by the {@link #run run} method.
*
* @param retries the current value of a counter that starts out at zero
* and is incremented when each retry is performed.
*
* @param maxRetries the maximum retries to be performed. By default,
* this value is set to {@link #getMaxRetries}. This method may return a
* different maximum retries value to override that default.
*
* @return the maximum number of retries to perform. The
* default policy is to return the {@code maxRetries} parameter value
* if the {@code exception} parameter value is an instance of {@link
* LockConflictException}.
*
* @throws Exception to cause the exception to be thrown by the {@link
* #run run} method. The default policy is to throw the {@code exception}
* parameter value if it is not an instance of {@link
* LockConflictException}.
*
* @since 3.4
*/
public int handleException(Exception exception,
int retries,
int maxRetries)
throws Exception {
if (exception instanceof LockConflictException) {
return maxRetries;
} else {
throw exception;
}
}
}