All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.multiverse.templates.TransactionTemplate Maven / Gradle / Ivy

Go to download

Contains the core interfaces/classes of the Multiverse project. So no STM implementations

There is a newer version: 0.7.0
Show newest version
package org.multiverse.templates;

import org.multiverse.api.*;
import org.multiverse.api.backoff.BackoffPolicy;
import org.multiverse.api.exceptions.*;
import org.multiverse.api.latches.CheapLatch;
import org.multiverse.api.latches.Latch;
import org.multiverse.api.latches.StandardLatch;
import org.multiverse.api.lifecycle.TransactionLifecycleEvent;
import org.multiverse.api.lifecycle.TransactionLifecycleListener;

import static java.lang.String.format;
import static org.multiverse.api.GlobalStmInstance.getGlobalStmInstance;
import static org.multiverse.api.ThreadLocalTransaction.getThreadLocalTransaction;
import static org.multiverse.api.ThreadLocalTransaction.setThreadLocalTransaction;

/**
 * A Template that handles the boilerplate code for transactions. A transaction will be started if none is available
 * around a section and if all goes right, the transaction is committed at the end.
 * 

* example: *

 * new TransactionTemplate(){
 *    Object execute(Transaction t){
 *        queue.push(1);
 *        return null;
 *    }
 * }.execute();
 * 
*

*

TransactionFactories

Some of the methods of the TransactionTemplate don't need a {@link TransactionFactory} * and createReference read tracking update transaction by default. But if you want a better performance, you need to pass your * own TransactionFactory. One of the biggest reasons is that the system else is not able to do runtime optimizations * like selecting better transaction implementation. *

*

Ignoring ThreadLocalTransaction

Out of the box the TransactionTemplate checks the {@link * ThreadLocalTransaction} for a running transaction, and if one is available, it doesn't createReference its own. But in some * cases you don't want to rely on a threadlocal, for example when you are not using instrumentation, it is possible to * totally ignore the ThreadLocalTransaction. *

*

Exceptions

All uncaught throwable's lead to a rollback of the transaction. *

*

Threadsafe

AtomicTemplates are thread-safe to use and can be reused. * * @author Peter Veentjer */ public abstract class TransactionTemplate { private final boolean threadLocalAware; private final boolean lifecycleListenersEnabled; private final TransactionFactory txFactory; /** * Creates a new TransactionTemplate that uses the STM stored in the GlobalStmInstance and works the the {@link * org.multiverse.api.ThreadLocalTransaction}. It automatically creates update transactions that are able to track * reads. *

* This constructor should only be used for experimentation purposes. If you want something fast, you need to pass * in a TransactionFactory. */ public TransactionTemplate() { this(getGlobalStmInstance()); } /** * Creates a new TransactionTemplate using the provided STM. The transaction used is stores/retrieved from the * {@link org.multiverse.api.ThreadLocalTransaction}. *

* It automatically creats read tracking update transactions. *

* This constructor should only be used for experimentation purposes. If you want something fast, you need to pass * in a TransactionFactory. * * @param stm the stm to use for transactions. * @throws NullPointerException if stm is null. */ public TransactionTemplate(Stm stm) { this(stm.getTransactionFactoryBuilder() .setReadonly(false) .setReadTrackingEnabled(true).build()); } /** * Creates a new TransactionTemplate with the provided TransactionFactory and that is aware of the * ThreadLocalTransaction. * * @param txFactory the TransactionFactory used to createReference Transactions. * @throws NullPointerException if txFactory is null. */ public TransactionTemplate(TransactionFactory txFactory) { this(txFactory, true, true, true); } /** * Creates a new TransactionTemplate with the provided TransactionFactory. * * @param txFactory the TransactionFactory used to createReference Transactions. * @param threadLocalAware true if this TransactionTemplate should look at the ThreadLocalTransaction and publish * the running transaction there. * @param lifecycleListenersEnabled true if the lifecycle callback methods should be enabled. Enabling them causes * extra object creation, so if you want to squeeze out more performance, set this to false at the price of * not having the lifecycle methods working. */ public TransactionTemplate(TransactionFactory txFactory, boolean threadLocalAware, boolean lifecycleListenersEnabled, boolean retryEnabled) { if (txFactory == null) { throw new NullPointerException(); } this.lifecycleListenersEnabled = lifecycleListenersEnabled; this.txFactory = txFactory; this.threadLocalAware = threadLocalAware; } /** * Checks if the TransactionTemplate should work together with the ThreadLocalTransaction. * * @return true if the TransactionTemplate should work together with the ThreadLocalTransaction. */ public final boolean isThreadLocalAware() { return threadLocalAware; } /** * Checks if the lifecycle listeners on this TransactionTemplate have been enabled. Disabling it reduces * object creation. * * @return true if the lifecycle listener has been enabled, false otherwise. */ public final boolean isLifecycleListenersEnabled() { return lifecycleListenersEnabled; } /** * Returns the TransactionFactory this TransactionTemplate uses to createReference Transactions. * * @return the TransactionFactory this TransactionTemplate uses to createReference Transactions. */ public final TransactionFactory getTransactionFactory() { return txFactory; } /** * This is the method that needs to be implemented. * * @param tx the transaction used for this execution. * @return the result of the execution. * @throws Exception the Exception thrown */ public abstract E execute(Transaction tx) throws Exception; /** * Lifecycle method that is called when this TransactionTemplate is about to begin. It will be called once. *

* If this TransactionTemplate doesn't starts its own transaction, this method won't be called. *

* If an exception is thrown while executing this method, the execute and transaction will abort. */ protected void onInit() { } /** * Lifecycle method that is called every time the transaction is started. It could be that a transaction is retried * (for example because of a read or write conflict or because of a blocking transaction). *

* If this TransactionTemplate doesn't starts its own transaction, this method won't be called. *

* If an exception is thrown while executing this method, the execute and transaction will abort. * * @param tx the Transaction that is started. */ protected void onStart(Transaction tx) { } /** * Lifecycle method that is called when this TransactionTemplate wants to execute. *

* If this TransactionTemplate doesn't starts its own transaction, this method won't be called. *

* It could be that this method is called 0 to n times. 0 if the transaction didn't made it to the commit part, and * for every attempt to commit. *

* If this method throws a Throwable, the transaction will not be committed but aborted instead. *

* It could be that this method is called more than once because a template could be retrying the transaction. * * @param tx the Transaction that wants to commit. */ protected void onPreCommit(Transaction tx) { } /** * Lifecycle method that is called when this TransactionTemplate has executed a commit. *

* If an Throwable is thrown, the changes remain committed back but it could lead to not notifying all explicitly * registered TransactionLifecycleListener. *

* If this TransactionTemplate doesn't starts its own transaction, this method won't be called. *

* This method will receive 1 call at the most, unless the template is reused of course. */ protected void onPostCommit() { } /** * Transaction Lifecycle method that is called after a transaction was aborted. *

* If an Error/RuntimeException is thrown, it could lead to not notifying all explicitly registered * TransactionLifecycleListener. But the transaction will always abort. *

* If this TransactionTemplate doesn't starts its own transaction, this method won't be called. *

* It could be that this method is called more than once because a template could be retrying the transaction. * * @param tx the transaction that currently is in the process of being aborted. */ protected void onPreAbort(Transaction tx) { } /** * Transaction lifecycle method that is called before an abort is executed. *

* If an Error/RuntimeException is thrown, it could lead to not notifying all explicitly registered * TransactionLifecycleListener. *

* If this TransactionTemplate doesn't starts its own transaction, this method won't be called. *

* It could be that this method is called more than once because a template could be retrying the transaction. */ protected void onPostAbort() { } /** * Executes the template. This is the method you want to call if you are using the template. * * @return the result of the {@link #execute(org.multiverse.api.Transaction)} method. * @throws InvisibleCheckedException if a checked exception was thrown while executing the {@link * #execute(org.multiverse.api.Transaction)} method. * @throws org.multiverse.api.exceptions.DeadTransactionException * if the exception was explicitly aborted. * @throws TooManyRetriesException if the template retried the transaction too many times. The cause of the last * failure (also an exception) is included as cause. So you have some idea where * to look for problems */ public final E execute() { try { return executeChecked(); } catch (Exception ex) { if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else { throw new TransactionTemplate.InvisibleCheckedException(ex); } } } /** * Executes the Template and rethrows the checked exception instead of wrapping it in a InvisibleCheckedException. * * @return the result * @throws Exception the Exception thrown inside the {@link #execute(org.multiverse.api.Transaction)} * method. * @throws org.multiverse.api.exceptions.DeadTransactionException * if the exception was explicitly aborted. * @throws TooManyRetriesException if the template retried the transaction too many times. The cause of the last * failure (also an exception) is included as cause. So you have some idea where to * look for problems */ public final E executeChecked() throws Exception { Transaction tx = threadLocalAware ? getThreadLocalTransaction() : null; if (noActiveTransaction(tx)) { return executeWithTransaction(); } else { return execute(tx); } } private E executeWithTransaction() throws Exception { CallbackListener listener = lifecycleListenersEnabled ? new CallbackListener() : null; Transaction tx = txFactory.start(); if (threadLocalAware) { setThreadLocalTransaction(tx); } Throwable lastFailureCause = null; if (lifecycleListenersEnabled) { onInit(); } try { do { tx.setAttempt(tx.getAttempt() + 1); try { try { if (listener != null) { tx.registerLifecycleListener(listener); } onStart(tx); E result = execute(tx); tx.commit(); return result; } catch (Retry e) { if (tx.getAttempt() - 1 < tx.getConfiguration().getMaxRetries()) { Latch latch; if (tx.getRemainingTimeoutNs() == Long.MAX_VALUE) { latch = new CheapLatch(); } else { latch = new StandardLatch(); } tx.registerRetryLatch(latch); tx.abort(); if (tx.getRemainingTimeoutNs() == Long.MAX_VALUE) { if (tx.getConfiguration().isInterruptible()) { latch.await(); } else { latch.awaitUninterruptible(); } } else { long beginNs = System.nanoTime(); boolean timeout; if (tx.getConfiguration().isInterruptible()) { timeout = !latch.tryAwaitNs(tx.getRemainingTimeoutNs()); } else { timeout = !latch.tryAwaitNs(tx.getRemainingTimeoutNs()); } long durationNs = beginNs - System.nanoTime(); tx.setRemainingTimeoutNs(tx.getRemainingTimeoutNs() - durationNs); if (timeout) { String msg = format("Transaction %s has timed with a total timeout of %s ns", tx.getConfiguration().getFamilyName(), tx.getConfiguration().getTimeoutNs()); throw new RetryTimeoutException(msg); } } tx.restart(); } } } catch (SpeculativeConfigurationFailure ex) { Transaction oldTransaction = tx; tx = txFactory.start(); tx.setAttempt(oldTransaction.getAttempt()); tx.setRemainingTimeoutNs(oldTransaction.getRemainingTimeoutNs()); if (threadLocalAware) { setThreadLocalTransaction(tx); } } catch (ControlFlowError er) { BackoffPolicy backoffPolicy = tx.getConfiguration().getBackoffPolicy(); backoffPolicy.delayedUninterruptible(tx); tx.restart(); } } while (tx.getAttempt() - 1 < tx.getConfiguration().getMaxRetries()); String msg = format("Too many retries on transaction '%s', maxRetries = %s", tx.getConfiguration().getFamilyName(), tx.getConfiguration().getMaxRetries()); throw new TooManyRetriesException(msg, lastFailureCause); } finally { if (tx != null && tx.getStatus() != TransactionStatus.committed) { tx.abort(); } if (threadLocalAware) { setThreadLocalTransaction(null); } } } private boolean noActiveTransaction(Transaction t) { return t == null || t.getStatus() != TransactionStatus.active; } public static class InvisibleCheckedException extends RuntimeException { static final long serialVersionUID = 0; public InvisibleCheckedException(Exception cause) { super(cause); } @Override public Exception getCause() { return (Exception) super.getCause(); } } private class CallbackListener implements TransactionLifecycleListener { @Override public void notify(Transaction tx, TransactionLifecycleEvent event) { switch (event) { case preAbort: onPreAbort(tx); break; case postAbort: onPostAbort(); break; case preCommit: onPreCommit(tx); break; case postCommit: onPostCommit(); break; default: throw new IllegalStateException(); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy