/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 2000-2003
* Sleepycat Software. All rights reserved.
*
* $Id: TransactionRunner.java,v 1.17 2003/10/21 13:36:59 gburd Exp $
*/
package com.sleepycat.bdb;
import com.sleepycat.db.DbException;
import com.sleepycat.db.DbEnv;
import com.sleepycat.db.DbDeadlockException;
import com.sleepycat.db.DbTxn;
import com.sleepycat.bdb.CurrentTransaction;
import com.sleepycat.bdb.util.ExceptionUnwrapper;
/**
* Starts a transaction, calls {@link TransactionWorker#doWork}, and handles
* transaction retry and exceptions.
*
* @author Mark Hayes
*/
public class TransactionRunner {
private static final int DEFAULT_MAX_RETRIES = 10;
private DbEnv env;
private CurrentTransaction currentTxn;
private int maxRetries;
private boolean dirtyRead;
private boolean noWait;
/**
* Creates a transaction runner for a given Berkeley DB environment.
* The default maximum number of retries (10) will be used.
*
* @param env is the environment for running transactions.
*/
public TransactionRunner(DbEnv env) {
this(env, DEFAULT_MAX_RETRIES);
}
/**
* 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.
*/
public TransactionRunner(DbEnv env, int maxRetries) {
this.env = env;
this.currentTxn = CurrentTransaction.getInstance(env);
this.maxRetries = maxRetries;
}
/**
* Returns the maximum number of retries that will be performed when
* deadlocks are detected.
*/
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.
*/
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
/**
* Returns whether transactions will read data that is modified by another
* transaction but not committed.
*/
public boolean getDirtyRead() {
return dirtyRead;
}
/**
* Changes whether transactions will read data that is modified by another
* transaction but not committed.
* Calling this method does not impact transaction already running.
*/
public void setDirtyRead(boolean dirtyRead) {
this.dirtyRead = dirtyRead;
}
/**
* Returns whether transactions will throw DbLockNotGrantedException
* instead of blocking when trying to access data that is locked by another
* transaction.
*/
public boolean getNoWait() {
return noWait;
}
/**
* Changes whether transactions will throw DbLockNotGrantedException
* instead of blocking when trying to access data that is locked by another
* transaction.
*/
public void setNoWait(boolean noWait) {
this.noWait = noWait;
}
/**
* Calls the {@link TransactionWorker#doWork} method and, for transactional
* environments, begins and ends a transaction. If the environment given
* is non-transactional, a transaction will not be used but the doWork()
* method will still be called.
*
* <p> In a transactional environment, a new transaction is started before
* calling doWork(). This will start a nested transaction if one is
* already active. If DbDeadlockException is thrown by doWork(), the
* transaction will be aborted and the process will be repeated up to the
* maximum number of retries specified. 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 this method. If
* no exception is thrown by doWork(), the transaction will be committed.
* This method will not attempt to commit or abort a transaction if it has
* already been committed or aborted by doWork(). </p>
*
* @throws DbDeadlockException 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 DbException, Exception {
if (currentTxn != null) { // if environment is transactional
boolean dr = dirtyRead; // these values should not be changed
boolean nw = noWait; // until this method returns
for (int i = 0;; i += 1) {
DbTxn txn = null;
try {
txn = currentTxn.beginTxn(dr, nw);
worker.doWork();
if (txn != null && txn == currentTxn.getTxn())
currentTxn.commitTxn();
return;
} catch (Exception e) {
e = ExceptionUnwrapper.unwrap(e);
if (txn != null && txn == currentTxn.getTxn()) {
try {
currentTxn.abortTxn();
} catch (Exception e2) {
// If we cannot abort, better to throw e than e2
// since it contains more information about the
// real problem.
System.err.println(e2.toString());
throw e;
}
}
if (i >= maxRetries || !(e instanceof DbDeadlockException))
throw e;
}
}
} else {
try {
worker.doWork();
} catch (Exception e) {
throw ExceptionUnwrapper.unwrap(e);
}
}
}
}