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

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

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

import org.multiverse.MultiverseConstants;
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;

/**
 * TransactionBoilerplate contains the boilerplate logic to deal with transaction and needs to
 * be used in combination with the TransactionalCallable.
 * 

* example: *

 * new TransactionLogic().execute(new TransactionalCallable {
 *         Integer call(){
 *            return queue.pop();
 *         }
 * }).execute();
 * 
*

* TransactionLogic is threadsafe and is designed to be reused. In case of transactional actors, * you can create a TransactionLogic object per class of actors (so static field) or per actor * (instance field). * * @author Sai Venkat * @author Peter Veentjer. */ public final class TransactionBoilerplate implements MultiverseConstants { private final boolean threadLocalAware; private final TransactionFactory transactionFactory; private final TransactionLifecycleListener listener; /** * Creates a new TransactionLogic 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 TransactionBoilerplate() { this(getGlobalStmInstance()); } /** * Creates a new TransactionLogic using the provided STM. The transaction used is stores/retrieved from the * {@link org.multiverse.api.ThreadLocalTransaction}. *

* It automatically creates 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 TransactionBoilerplate(Stm stm) { this(stm.getTransactionFactoryBuilder() .build()); } /** * Creates a new TransactionLogic with the provided TransactionFactory and that is aware of the * ThreadLocalTransaction. * * @param transactionFactory the TransactionFactory used to createReference Transactions. * @throws NullPointerException if txFactory is null. */ public TransactionBoilerplate(TransactionFactory transactionFactory) { this(transactionFactory, null, true); } /** * Creates a new TransactionLogic with the provided TransactionFactory. * * @param transactionFactory the TransactionFactory used to createReference Transactions. * @param threadLocalAware true if this TransactionLogic should look at the ThreadLocalTransaction * and publish the running transaction there. * todo * @param listener 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 TransactionBoilerplate( TransactionFactory transactionFactory, TransactionLifecycleListener listener, boolean threadLocalAware) { if (transactionFactory == null) { throw new NullPointerException(); } this.listener = listener; this.transactionFactory = transactionFactory; this.threadLocalAware = threadLocalAware; } /** * Checks if the TransactionLogic should work together with the ThreadLocalTransaction. * * @return true if the TransactionLogic should work together with the ThreadLocalTransaction. */ public boolean isThreadLocalAware() { return threadLocalAware; } /** * Returns the {@link org.multiverse.api.lifecycle.TransactionLifecycleListener} * * @return the TransactionLifecycleListener. Is allowed to be null which indicates that no events * will be send. */ public TransactionLifecycleListener getTransactionLifecycleListener() { return listener; } /** * Returns the TransactionFactory this TransactionLogic uses to createReference Transactions. * * @return the TransactionFactory this TransactionLogic uses to createReference Transactions. */ public TransactionFactory getTransactionFactory() { return transactionFactory; } /** * 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 TransactionLogic 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) { } /** * Executes the TransactionalCallable. If the callable throws a checked exception, this exception * is wrapped inside a {@link org.multiverse.templates.InvisibleCheckedException}. * * @param callable the TransactionalCallable to execute. * @return the result. * @throws InvisibleCheckedException if a checked exception was thrown while executing the callable. * @throws org.multiverse.api.exceptions.DeadTransactionException * if the exception was explicitly aborted. * @throws org.multiverse.api.exceptions.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 * @throws NullPointerException if callable is null. */ public E execute(TransactionalCallable callable) { if (callable == null) { throw new NullPointerException("callable can't be null"); } try { return executeChecked(callable); } catch (Exception ex) { if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } else { throw new InvisibleCheckedException(ex); } } } /** * Executes the Template. * * @param callable the callable to execute. * @return the result * @throws org.multiverse.api.exceptions.DeadTransactionException * if the exception was explicitly aborted. * @throws org.multiverse.api.exceptions.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 * @throws NullPointerException if callable is null. * @throws Exception if the callable throws an exception. */ public E executeChecked(TransactionalCallable callable) throws Exception { if (callable == null) { throw new NullPointerException("callable can't be null"); } Transaction tx = threadLocalAware ? getThreadLocalTransaction() : null; switch (transactionFactory.getTransactionConfiguration().getPropagationLevel()) { case Requires: if (!isDead(tx)) { return callable.call(tx); } else { if (sameTransactionFactory(tx)) { tx.reset(); tx.setAttempt(0); tx.setRemainingTimeoutNs(tx.getConfiguration().getTimeoutNs()); } else { tx = transactionFactory.create(); } if (threadLocalAware) { setThreadLocalTransaction(tx); } return executeWithTransaction(tx, callable); } case Mandatory: if (isDead(tx)) { throw new NoTransactionFoundException(); } return callable.call(tx); case RequiresNew: Transaction suspendedTx = tx; try { tx = transactionFactory.create(); if (threadLocalAware) { setThreadLocalTransaction(tx); } return executeWithTransaction(tx, callable); } finally { if (threadLocalAware) { setThreadLocalTransaction(suspendedTx); } } case Never: if (!isDead(tx)) { throw new NoTransactionAllowedException(); } return callable.call(null); case Supports: if (isDead(tx)) { return callable.call(null); } else { return callable.call(tx); } default: throw new IllegalStateException(); } } private boolean sameTransactionFactory(Transaction tx) { return tx != null && tx.getTransactionFactory() == transactionFactory; } private E executeWithTransaction(Transaction tx, TransactionalCallable callable) throws Exception { Throwable lastFailureCause = null; if (listener != null) { listener.notify(tx, TransactionLifecycleEvent.PostStart); } try { do { tx.setAttempt(tx.getAttempt() + 1); try { try { logStart(tx); if (listener != null) { tx.registerLifecycleListener(listener); } onStart(tx); E result = callable.call(tx); tx.commit(); return result; } catch (Retry e) { handleRetry(tx); } } catch (SpeculativeConfigurationFailure ex) { tx = handleSpeculativeConfigurationFailure(tx); } catch (ControlFlowError er) { handleControlFlowError(tx, er); } } 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(); } } } private static void logStart(Transaction tx) { if (___TRACING_ENABLED) { if (tx.getConfiguration().getTraceLevel().isLogableFrom(TraceLevel.course)) { System.out.println(tx.getConfiguration().getFamilyName() + " starting"); } } } private static void handleControlFlowError(Transaction tx, ControlFlowError er) { if (___TRACING_ENABLED) { if (tx.getConfiguration().getTraceLevel().isLogableFrom(TraceLevel.course)) { System.out.println(tx.getConfiguration().getFamilyName() + " " + er.getDescription()); } } BackoffPolicy backoffPolicy = tx.getConfiguration().getBackoffPolicy(); backoffPolicy.delayedUninterruptible(tx); tx.reset(); } private Transaction handleSpeculativeConfigurationFailure(Transaction oldTx) { if (___TRACING_ENABLED) { if (oldTx.getConfiguration().getTraceLevel().isLogableFrom(TraceLevel.course)) { System.out.println(oldTx.getConfiguration().getFamilyName() + " speculative configuration failure"); } } oldTx.abort(); Transaction newTx = transactionFactory.create(); newTx.setAttempt(oldTx.getAttempt()); newTx.setRemainingTimeoutNs(oldTx.getRemainingTimeoutNs()); if (threadLocalAware) { setThreadLocalTransaction(newTx); } return newTx; } private static void handleRetry(Transaction tx) throws InterruptedException { if (___TRACING_ENABLED) { if (tx.getConfiguration().getTraceLevel().isLogableFrom(TraceLevel.course)) { System.out.println(tx.getConfiguration().getFamilyName() + " retry (blocking)"); } } 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 = System.nanoTime() - beginNs; 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.reset(); } } private static boolean isDead(Transaction t) { return t == null || t.getStatus().isDead(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy