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