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