001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2000,2008 Oracle. All rights reserved.
005: *
006: * $Id: TransactionRunner.java,v 1.44.2.5 2008/01/07 15:14:06 cwl Exp $
007: */
008:
009: package com.sleepycat.collections;
010:
011: import com.sleepycat.compat.DbCompat;
012: import com.sleepycat.je.DatabaseException;
013: import com.sleepycat.je.DeadlockException;
014: import com.sleepycat.je.Environment;
015: import com.sleepycat.je.Transaction;
016: import com.sleepycat.je.TransactionConfig;
017: import com.sleepycat.util.ExceptionUnwrapper;
018:
019: /**
020: * Starts a transaction, calls {@link TransactionWorker#doWork}, and handles
021: * transaction retry and exceptions. To perform a transaction, the user
022: * implements the {@link TransactionWorker} interface and passes an instance of
023: * that class to the {@link #run} method.
024: *
025: * <p>A single TransactionRunner instance may be used by any number of threads
026: * for any number of transactions.</p>
027: *
028: * <p>The behavior of the run() method depends on whether the environment is
029: * transactional, whether nested transactions are enabled, and whether a
030: * transaction is already active.</p>
031: *
032: * <ul>
033: * <li>When the run() method is called in a transactional environment and no
034: * transaction is active for the current thread, a new transaction is started
035: * before calling doWork(). If DeadlockException is thrown by doWork(), the
036: * transaction will be aborted and the process will be repeated up to the
037: * maximum number of retries. If another exception is thrown by doWork() or
038: * the maximum number of retries has occurred, the transaction will be aborted
039: * and the exception will be rethrown by the run() method. If no exception is
040: * thrown by doWork(), the transaction will be committed. The run() method
041: * will not attempt to commit or abort a transaction if it has already been
042: * committed or aborted by doWork().</li>
043: *
044: * <li>When the run() method is called and a transaction is active for the
045: * current thread, and nested transactions are enabled, a nested transaction is
046: * started before calling doWork(). The transaction that is active when
047: * calling the run() method will become the parent of the nested transaction.
048: * The nested transaction will be committed or aborted by the run() method
049: * following the same rules described above. Note that nested transactions may
050: * not be enabled for the JE product, since JE does not support nested
051: * transactions.</li>
052: *
053: * <li>When the run() method is called in a non-transactional environment, the
054: * doWork() method is called without starting a transaction. The run() method
055: * will return without committing or aborting a transaction, and any exceptions
056: * thrown by the doWork() method will be thrown by the run() method.</li>
057: *
058: * <li>When the run() method is called and a transaction is active for the
059: * current thread and nested transactions are not enabled (the default) the
060: * same rules as above apply. All the operations performed by the doWork()
061: * method will be part of the currently active transaction.</li>
062: * </ul>
063: *
064: * <p>In a transactional environment, the rules described above support nested
065: * calls to the run() method and guarantee that the outermost call will cause
066: * the transaction to be committed or aborted. This is true whether or not
067: * nested transactions are supported or enabled. Note that nested transactions
068: * are provided as an optimization for improving concurrency but do not change
069: * the meaning of the outermost transaction. Nested transactions are not
070: * currently supported by the JE product.</p>
071: *
072: * @author Mark Hayes
073: */
074: public class TransactionRunner {
075:
076: /** The default maximum number of retries. */
077: public static final int DEFAULT_MAX_RETRIES = 10;
078:
079: private CurrentTransaction currentTxn;
080: private int maxRetries;
081: private TransactionConfig config;
082: private boolean allowNestedTxn;
083:
084: /**
085: * Creates a transaction runner for a given Berkeley DB environment.
086: * The default maximum number of retries ({@link #DEFAULT_MAX_RETRIES}) and
087: * a null (default) {@link TransactionConfig} will be used.
088: *
089: * @param env is the environment for running transactions.
090: */
091: public TransactionRunner(Environment env) {
092:
093: this (env, DEFAULT_MAX_RETRIES, null);
094: }
095:
096: /**
097: * Creates a transaction runner for a given Berkeley DB environment and
098: * with a given number of maximum retries.
099: *
100: * @param env is the environment for running transactions.
101: *
102: * @param maxRetries is the maximum number of retries that will be
103: * performed when deadlocks are detected.
104: *
105: * @param config the transaction configuration used for calling
106: * {@link Environment#beginTransaction}, or null to use the default
107: * configuration. The configuration object is not cloned, and
108: * any modifications to it will impact subsequent transactions.
109: */
110: public TransactionRunner(Environment env, int maxRetries,
111: TransactionConfig config) {
112:
113: this .currentTxn = CurrentTransaction.getInstance(env);
114: this .maxRetries = maxRetries;
115: this .config = config;
116: }
117:
118: /**
119: * Returns the maximum number of retries that will be performed when
120: * deadlocks are detected.
121: */
122: public int getMaxRetries() {
123:
124: return maxRetries;
125: }
126:
127: /**
128: * Changes the maximum number of retries that will be performed when
129: * deadlocks are detected.
130: * Calling this method does not impact transactions already running.
131: */
132: public void setMaxRetries(int maxRetries) {
133:
134: this .maxRetries = maxRetries;
135: }
136:
137: /**
138: * Returns whether nested transactions will be created if
139: * <code>run()</code> is called when a transaction is already active for
140: * the current thread.
141: * By default this property is false.
142: *
143: * <p>Note that this method always returns false in the JE product, since
144: * nested transactions are not supported by JE.</p>
145: */
146: public boolean getAllowNestedTransactions() {
147:
148: return allowNestedTxn;
149: }
150:
151: /**
152: * Changes whether nested transactions will be created if
153: * <code>run()</code> is called when a transaction is already active for
154: * the current thread.
155: * Calling this method does not impact transactions already running.
156: *
157: * <p>Note that true may not be passed to this method in the JE product,
158: * since nested transactions are not supported by JE.</p>
159: */
160: public void setAllowNestedTransactions(boolean allowNestedTxn) {
161:
162: if (allowNestedTxn && !DbCompat.NESTED_TRANSACTIONS) {
163: throw new UnsupportedOperationException(
164: "Nested transactions are not supported.");
165: }
166: this .allowNestedTxn = allowNestedTxn;
167: }
168:
169: /**
170: * Returns the transaction configuration used for calling
171: * {@link Environment#beginTransaction}.
172: *
173: * <p>If this property is null, the default configuration is used. The
174: * configuration object is not cloned, and any modifications to it will
175: * impact subsequent transactions.</p>
176: *
177: * @return the transaction configuration.
178: */
179: public TransactionConfig getTransactionConfig() {
180:
181: return config;
182: }
183:
184: /**
185: * Changes the transaction configuration used for calling
186: * {@link Environment#beginTransaction}.
187: *
188: * <p>If this property is null, the default configuration is used. The
189: * configuration object is not cloned, and any modifications to it will
190: * impact subsequent transactions.</p>
191: *
192: * @param config the transaction configuration.
193: */
194: public void setTransactionConfig(TransactionConfig config) {
195:
196: this .config = config;
197: }
198:
199: /**
200: * Calls the {@link TransactionWorker#doWork} method and, for transactional
201: * environments, may begin and end a transaction. If the environment given
202: * is non-transactional, a transaction will not be used but the doWork()
203: * method will still be called. See the class description for more
204: * information.
205: *
206: * @throws DeadlockException when it is thrown by doWork() and the
207: * maximum number of retries has occurred. The transaction will have been
208: * aborted by this method.
209: *
210: * @throws Exception when any other exception is thrown by doWork(). The
211: * exception will first be unwrapped by calling {@link
212: * ExceptionUnwrapper#unwrap}. The transaction will have been aborted by
213: * this method.
214: */
215: public void run(TransactionWorker worker) throws DatabaseException,
216: Exception {
217:
218: if (currentTxn != null
219: && (allowNestedTxn || currentTxn.getTransaction() == null)) {
220:
221: /*
222: * Transactional and (not nested or nested txns allowed).
223: */
224: for (int i = 0;; i += 1) {
225: Transaction txn = null;
226: try {
227: txn = currentTxn.beginTransaction(config);
228: worker.doWork();
229: if (txn != null
230: && txn == currentTxn.getTransaction()) {
231: currentTxn.commitTransaction();
232: }
233: return;
234: } catch (Throwable e) {
235: e = ExceptionUnwrapper.unwrapAny(e);
236: if (txn != null
237: && txn == currentTxn.getTransaction()) {
238: try {
239: currentTxn.abortTransaction();
240: } catch (Throwable e2) {
241:
242: /*
243: * We print this stack trace so that the
244: * information is not lost when we throw the
245: * original exception.
246: */
247: if (DbCompat.TRANSACTION_RUNNER_PRINT_STACK_TRACES) {
248: e2.printStackTrace();
249: }
250: /* Force the original exception to be thrown. */
251: i = maxRetries + 1;
252: }
253: }
254: if (i >= maxRetries
255: || !(e instanceof DeadlockException)) {
256: if (e instanceof Exception) {
257: throw (Exception) e;
258: } else {
259: throw (Error) e;
260: }
261: }
262: }
263: }
264: } else {
265:
266: /*
267: * Non-transactional or (nested and no nested txns allowed).
268: */
269: try {
270: worker.doWork();
271: } catch (Exception e) {
272: throw ExceptionUnwrapper.unwrap(e);
273: }
274: }
275: }
276: }
|