• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /asuswrt-rt-n18u-9.0.0.4.380.2695/release/src-rt/router/db-4.8.30/java/src/com/sleepycat/collections/
1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2000-2009 Oracle.  All rights reserved.
5 *
6 * $Id$
7 */
8
9package com.sleepycat.collections;
10
11import com.sleepycat.compat.DbCompat;
12import com.sleepycat.db.DatabaseException;
13import com.sleepycat.db.DeadlockException;
14import com.sleepycat.db.Environment;
15import com.sleepycat.db.Transaction;
16import com.sleepycat.db.TransactionConfig;
17import com.sleepycat.util.ExceptionUnwrapper;
18
19/**
20 * Starts a transaction, calls {@link TransactionWorker#doWork}, and handles
21 * transaction retry and exceptions.  To perform a transaction, the user
22 * implements the {@link TransactionWorker} interface and passes an instance of
23 * that class to the {@link #run run} method.
24 *
25 * <p>A single TransactionRunner instance may be used by any number of threads
26 * for any number of transactions.</p>
27 *
28 * <p>The behavior of the run() method depends on whether the environment is
29 * transactional, whether nested transactions are enabled, and whether a
30 * transaction is already active.</p>
31 *
32 * <ul>
33 * <li>When the run() method is called in a transactional environment and no
34 * transaction is active for the current thread, a new transaction is started
35 * before calling doWork().  If DeadlockException is thrown by doWork(),
36 * the transaction will be aborted and the process will be repeated up to the
37 * maximum number of retries.  If another exception is thrown by doWork() or
38 * the maximum number of retries has occurred, the transaction will be aborted
39 * and the exception will be rethrown by the run() method.  If no exception is
40 * thrown by doWork(), the transaction will be committed.  The run() method
41 * will not attempt to commit or abort a transaction if it has already been
42 * committed or aborted by doWork().</li>
43 *
44 * <li>When the run() method is called and a transaction is active for the
45 * current thread, and nested transactions are enabled, a nested transaction is
46 * started before calling doWork().  The transaction that is active when
47 * calling the run() method will become the parent of the nested transaction.
48 * The nested transaction will be committed or aborted by the run() method
49 * following the same rules described above.  Note that nested transactions may
50 * not be enabled for the JE product, since JE does not support nested
51 * transactions.</li>
52 *
53 * <li>When the run() method is called in a non-transactional environment, the
54 * doWork() method is called without starting a transaction.  The run() method
55 * will return without committing or aborting a transaction, and any exceptions
56 * thrown by the doWork() method will be thrown by the run() method.</li>
57 *
58 * <li>When the run() method is called and a transaction is active for the
59 * current thread and nested transactions are not enabled (the default) the
60 * same rules as above apply. All the operations performed by the doWork()
61 * method will be part of the currently active transaction.</li>
62 * </ul>
63 *
64 * <p>In a transactional environment, the rules described above support nested
65 * calls to the run() method and guarantee that the outermost call will cause
66 * the transaction to be committed or aborted.  This is true whether or not
67 * nested transactions are supported or enabled.  Note that nested transactions
68 * are provided as an optimization for improving concurrency but do not change
69 * the meaning of the outermost transaction.  Nested transactions are not
70 * currently supported by the JE product.</p>
71 *
72 * @author Mark Hayes
73 */
74public class TransactionRunner {
75
76    /** The default maximum number of retries. */
77    public static final int DEFAULT_MAX_RETRIES = 10;
78
79    private CurrentTransaction currentTxn;
80    private int maxRetries;
81    private TransactionConfig config;
82    private boolean allowNestedTxn;
83
84    /**
85     * Creates a transaction runner for a given Berkeley DB environment.
86     * The default maximum number of retries ({@link #DEFAULT_MAX_RETRIES}) and
87     * a null (default) {@link TransactionConfig} will be used.
88     *
89     * @param env is the environment for running transactions.
90     */
91    public TransactionRunner(Environment env) {
92
93        this(env, DEFAULT_MAX_RETRIES, null);
94    }
95
96    /**
97     * Creates a transaction runner for a given Berkeley DB environment and
98     * with a given number of maximum retries.
99     *
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,
111			     int maxRetries,
112                             TransactionConfig config) {
113
114        this.currentTxn = CurrentTransaction.getInstance(env);
115        this.maxRetries = maxRetries;
116        this.config = config;
117    }
118
119    /**
120     * Returns the maximum number of retries that will be performed when
121     * deadlocks are detected.
122     */
123    public int getMaxRetries() {
124
125        return maxRetries;
126    }
127
128    /**
129     * Changes the maximum number of retries that will be performed when
130     * deadlocks are detected.
131     * Calling this method does not impact transactions already running.
132     */
133    public void setMaxRetries(int maxRetries) {
134
135        this.maxRetries = maxRetries;
136    }
137
138    /**
139     * Returns whether nested transactions will be created if
140     * <code>run()</code> is called when a transaction is already active for
141     * the current thread.
142     * By default this property is false.
143     *
144     * <p>Note that this method always returns false in the JE product, since
145     * nested transactions are not supported by JE.</p>
146     */
147    public boolean getAllowNestedTransactions() {
148
149        return allowNestedTxn;
150    }
151
152    /**
153     * Changes whether nested transactions will be created if
154     * <code>run()</code> is called when a transaction is already active for
155     * the current thread.
156     * Calling this method does not impact transactions already running.
157     *
158     * <p>Note that true may not be passed to this method in the JE product,
159     * since nested transactions are not supported by JE.</p>
160     */
161    public void setAllowNestedTransactions(boolean allowNestedTxn) {
162
163        if (allowNestedTxn && !DbCompat.NESTED_TRANSACTIONS) {
164            throw new UnsupportedOperationException(
165                    "Nested transactions are not supported.");
166        }
167        this.allowNestedTxn = allowNestedTxn;
168    }
169
170    /**
171     * Returns the transaction configuration used for calling
172     * {@link Environment#beginTransaction}.
173     *
174     * <p>If this property is null, the default configuration is used.  The
175     * configuration object is not cloned, and any modifications to it will
176     * impact subsequent transactions.</p>
177     *
178     * @return the transaction configuration.
179     */
180    public TransactionConfig getTransactionConfig() {
181
182        return config;
183    }
184
185    /**
186     * Changes the transaction configuration used for calling
187     * {@link Environment#beginTransaction}.
188     *
189     * <p>If this property is null, the default configuration is used.  The
190     * configuration object is not cloned, and any modifications to it will
191     * impact subsequent transactions.</p>
192     *
193     * @param config the transaction configuration.
194     */
195    public void setTransactionConfig(TransactionConfig config) {
196
197        this.config = config;
198    }
199
200    /**
201     * Calls the {@link TransactionWorker#doWork} method and, for transactional
202     * environments, may begin and end a transaction.  If the environment given
203     * is non-transactional, a transaction will not be used but the doWork()
204     * method will still be called.  See the class description for more
205     * information.
206     *
207     * @throws DeadlockException when it is thrown by doWork() and the
208     * maximum number of retries has occurred.  The transaction will have been
209     * aborted by this method.
210     *
211     * @throws Exception when any other exception is thrown by doWork().  The
212     * exception will first be unwrapped by calling {@link
213     * ExceptionUnwrapper#unwrap}.  The transaction will have been aborted by
214     * this method.
215     */
216    public void run(TransactionWorker worker)
217        throws DatabaseException, Exception {
218
219        if (currentTxn != null &&
220            (allowNestedTxn || currentTxn.getTransaction() == null)) {
221            /* Transactional and (not nested or nested txns allowed). */
222            int useMaxRetries = maxRetries;
223            for (int retries = 0;; retries += 1) {
224                Transaction txn = null;
225                try {
226                    txn = currentTxn.beginTransaction(config);
227                    worker.doWork();
228                    if (txn != null && txn == currentTxn.getTransaction()) {
229                        currentTxn.commitTransaction();
230                    }
231                    return;
232                } catch (Throwable e) {
233                    e = ExceptionUnwrapper.unwrapAny(e);
234                    if (txn != null && txn == currentTxn.getTransaction()) {
235                        try {
236                            currentTxn.abortTransaction();
237                        } catch (Throwable e2) {
238
239                            /*
240                             * We print this stack trace so that the
241                             * information is not lost when we throw the
242                             * original exception.
243                             */
244			    if (DbCompat.
245                                TRANSACTION_RUNNER_PRINT_STACK_TRACES) {
246				e2.printStackTrace();
247			    }
248                            /* Force the original exception to be thrown. */
249                            retries = useMaxRetries;
250                        }
251                    }
252                    /* An Error should not require special handling. */
253                    if (e instanceof Error) {
254                        throw (Error) e;
255                    }
256                    /* Allow a subclass to determine retry policy. */
257                    Exception ex = (Exception) e;
258                    useMaxRetries =
259                        handleException(ex, retries, useMaxRetries);
260                    if (retries >= useMaxRetries) {
261                        throw ex;
262                    }
263                }
264            }
265        } else {
266            /* Non-transactional or (nested and no nested txns allowed). */
267            try {
268                worker.doWork();
269            } catch (Exception e) {
270                throw ExceptionUnwrapper.unwrap(e);
271            }
272        }
273    }
274
275    /**
276     * Handles exceptions that occur during a transaction, and may implement
277     * transaction retry policy.  The transaction is aborted by the {@link
278     * #run run} method before calling this method.
279     *
280     * <p>The default implementation of this method throws the {@code
281     * exception} parameter if it is not an instance of {@link
282     * DeadlockException} and otherwise returns the {@code maxRetries}
283     * parameter value.  This method can be overridden to throw a different
284     * exception or return a different number of retries.  For example:</p>
285     * <ul>
286     * <li>This method could call {@code Thread.sleep} for a short interval to
287     * allow other transactions to finish.</li>
288     *
289     * <li>This method could return a different {@code maxRetries} value
290     * depending on the {@code exception} that occurred.</li>
291     *
292     * <li>This method could throw an application-defined exception when the
293     * {@code retries} value is greater or equal to the {@code maxRetries} and
294     * a {@link DeadlockException} occurs, to override the default behavior
295     * which is to throw the {@link DeadlockException}.</li>
296     * </ul>
297     *
298     * @param exception an exception that was thrown by the {@link
299     * TransactionWorker#doWork} method or thrown when beginning or committing
300     * the transaction.  If the {@code retries} value is greater or equal to
301     * {@code maxRetries} when this method returns normally, this exception
302     * will be thrown by the {@link #run run} method.
303     *
304     * @param retries the current value of a counter that starts out at zero
305     * and is incremented when each retry is performed.
306     *
307     * @param maxRetries the maximum retries to be performed.  By default,
308     * this value is set to {@link #getMaxRetries}.  This method may return a
309     * different maximum retries value to override that default.
310     *
311     * @return the maximum number of retries to perform.  The
312     * default policy is to return the {@code maxRetries} parameter value
313     * if the {@code exception} parameter value is an instance of {@link
314     * DeadlockException}.
315     *
316     * @throws Exception to cause the exception to be thrown by the {@link
317     * #run run} method.  The default policy is to throw the {@code exception}
318     * parameter value if it is not an instance of {@link
319     * DeadlockException}.
320     *
321     * @since 3.4
322     */
323    public int handleException(Exception exception,
324                               int retries,
325                               int maxRetries)
326        throws Exception {
327
328        if (exception instanceof DeadlockException) {
329            return maxRetries;
330        } else {
331            throw exception;
332        }
333    }
334}
335