/* * INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa * Copyright 2013 INESC-ID and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3.0 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.transaction.totalorder; import org.infinispan.CacheException; import org.infinispan.commands.tx.GMUPrepareCommand; import org.infinispan.commands.tx.PrepareCommand; import org.infinispan.context.Flag; import org.infinispan.context.impl.TxInvocationContext; import org.infinispan.executors.ConditionalExecutorService; import org.infinispan.executors.ConditionalRunnable; import org.infinispan.factories.annotations.Inject; import org.infinispan.interceptors.base.CommandInterceptor; import org.infinispan.jmx.annotations.MBean; import org.infinispan.jmx.annotations.ManagedAttribute; import org.infinispan.jmx.annotations.ManagedOperation; import org.infinispan.transaction.RemoteTransaction; import org.infinispan.transaction.TxDependencyLatch; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.jgroups.blocks.RequestHandler; import org.rhq.helpers.pluginAnnotations.agent.DisplayType; import org.rhq.helpers.pluginAnnotations.agent.Metric; import org.rhq.helpers.pluginAnnotations.agent.Units; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; /** * @author Pedro Ruivo * @author Mircea.markus@jboss.org * @since 5.2 */ @MBean(objectName = "ParallelTotalOrderManager", description = "Total order Manager used when the transactions are " + "committed in two phases") public class ParallelTotalOrderManager extends BaseTotalOrderManager { private static final Log log = LogFactory.getLog(ParallelTotalOrderManager.class); private final AtomicLong waitTimeInQueue = new AtomicLong(0); private final AtomicLong initializationDuration = new AtomicLong(0); /** * this map is used to keep track of concurrent transactions. */ private final ConcurrentMap<Object, TxDependencyLatch> keysLocked = new ConcurrentHashMap<Object, TxDependencyLatch>(); private ConditionalExecutorService validationExecutorService; @Inject public void inject(ConditionalExecutorService conditionalExecutorService) { validationExecutorService = conditionalExecutorService; } @Override public final Object processTransactionFromSequencer(PrepareCommand prepareCommand, TxInvocationContext ctx, CommandInterceptor invoker) { logAndCheckContext(prepareCommand, ctx); copyLookedUpEntriesToRemoteContext(ctx); RemoteTransaction remoteTransaction = (RemoteTransaction) ctx.getCacheTransaction(); ParallelPrepareProcessor ppp = constructParallelPrepareProcessor(prepareCommand, ctx, invoker, remoteTransaction); Set<TxDependencyLatch> previousTxs = new HashSet<TxDependencyLatch>(); Set<Object> keysModified = getModifiedKeyFromModifications(remoteTransaction.getModifications()); //this will collect all the count down latch corresponding to the previous transactions in the queue for (Object key : keysModified) { TxDependencyLatch prevTx = keysLocked.put(key, remoteTransaction.getDependencyLatch()); if (prevTx != null) { previousTxs.add(prevTx); } } if (prepareCommand instanceof GMUPrepareCommand) { for (Object key : ((GMUPrepareCommand) prepareCommand).getReadSet()) { previousTxs.add(keysLocked.get(key)); } previousTxs.remove(remoteTransaction.getDependencyLatch()); } ppp.setPreviousTransactions(previousTxs); if (trace) log.tracef("Transaction [%s] write set is %s", remoteTransaction.getDependencyLatch(), keysModified); try { validationExecutorService.execute(ppp); } catch (Exception e) { log.fatal("Executor service is not enabled!"); throw new RuntimeException(e); } return RequestHandler.DO_NOT_REPLY; } @Override public final void finishTransaction(RemoteTransaction remoteTransaction) { super.finishTransaction(remoteTransaction); for (Object key : getModifiedKeyFromModifications(remoteTransaction.getModifications())) { this.keysLocked.remove(key, remoteTransaction.getDependencyLatch()); } } @Override public Set<TxDependencyLatch> getPendingCommittingTransaction() { return new HashSet<TxDependencyLatch>(keysLocked.values()); } @ManagedOperation(description = "Resets the statistics") public void resetStatistics() { super.resetStatistics(); waitTimeInQueue.set(0); initializationDuration.set(0); } @ManagedAttribute(description = "Average time in the queue before the validation (milliseconds)") @Metric(displayName = "Average Waiting Duration In Queue", units = Units.MILLISECONDS, displayType = DisplayType.SUMMARY) public double getAverageWaitingTimeInQueue() { long time = waitTimeInQueue.get(); int tx = numberOfTxValidated.get(); if (tx == 0) { return 0; } return (time / tx) / 1000000.0; } @ManagedAttribute(description = "Average duration of a transaction initialization before validation, ie, " + "ensuring the order of transactions (milliseconds)") @Metric(displayName = "Average Initialization Duration", units = Units.MILLISECONDS, displayType = DisplayType.SUMMARY) public double getAverageInitializationDuration() { long time = initializationDuration.get(); int tx = numberOfTxValidated.get(); if (tx == 0) { return 0; } return (time / tx) / 1000000.0; } /** * constructs a new thread to be passed to the thread pool. this is overridden in distributed mode that has a * different behavior * * @param prepareCommand the prepare command * @param txInvocationContext the context * @param invoker the next interceptor * @param remoteTransaction the remote transaction * @return a new thread */ private ParallelPrepareProcessor constructParallelPrepareProcessor(PrepareCommand prepareCommand, TxInvocationContext txInvocationContext, CommandInterceptor invoker, RemoteTransaction remoteTransaction) { return new ParallelPrepareProcessor(prepareCommand, txInvocationContext, invoker, remoteTransaction); } /** * updates the accumulating time for profiling information * * @param creationTime the arrival timestamp of the prepare command to this component in remote * @param validationStartTime the processing start timestamp * @param validationEndTime the validation ending timestamp * @param initializationEndTime the initialization ending timestamp */ private void updateDurationStats(long creationTime, long validationStartTime, long validationEndTime, long initializationEndTime) { if (statisticsEnabled) { //set the profiling information waitTimeInQueue.addAndGet(validationStartTime - creationTime); initializationDuration.addAndGet(initializationEndTime - validationStartTime); processingDuration.addAndGet(validationEndTime - initializationEndTime); numberOfTxValidated.incrementAndGet(); } } /** * This class is used to validate transaction in repeatable read with write skew check */ private class ParallelPrepareProcessor implements ConditionalRunnable { protected final RemoteTransaction remoteTransaction; protected final PrepareCommand prepareCommand; //the set of others transaction's count down latch (it will be unblocked when the transaction finishes) private final Set<TxDependencyLatch> previousTransactions; private final TxInvocationContext txInvocationContext; private final CommandInterceptor invoker; private long creationTime = -1; private long processStartTime = -1; private long initializationEndTime = -1; protected ParallelPrepareProcessor(PrepareCommand prepareCommand, TxInvocationContext txInvocationContext, CommandInterceptor invoker, RemoteTransaction remoteTransaction) { if (prepareCommand == null || txInvocationContext == null || invoker == null) { throw new IllegalArgumentException("Arguments must not be null"); } this.prepareCommand = prepareCommand; this.txInvocationContext = txInvocationContext; this.invoker = invoker; this.creationTime = now(); this.previousTransactions = new HashSet<TxDependencyLatch>(); this.remoteTransaction = remoteTransaction; } public void setPreviousTransactions(Set<TxDependencyLatch> previousTransactions) { this.previousTransactions.addAll(previousTransactions); } @Override public final void run() { processStartTime = now(); boolean exception = false; try { if (trace) log.tracef("Validating transaction %s ", prepareCommand.getGlobalTransaction().prettyPrint()); initializeValidation(); initializationEndTime = now(); //invoke next interceptor in the chain Object result = prepareCommand.acceptVisitor(txInvocationContext, invoker); prepareCommand.sendReply(result, false); } catch (Throwable t) { log.trace("Exception while processing the rest of the interceptor chain", t); if (initializationEndTime == -1) { initializationEndTime = now(); } prepareCommand.sendReply(t, true); exception = true; } finally { logProcessingFinalStatus(prepareCommand, exception); finishPrepare(exception); updateDurationStats(creationTime, processStartTime, now(), initializationEndTime); } } @Override public final boolean isReady() { previousTransactions.remove(remoteTransaction.getDependencyLatch()); for (TxDependencyLatch prevTx : previousTransactions) { try { if (!prevTx.await(0, TimeUnit.SECONDS)) { if (log.isTraceEnabled()) { log.tracef("[%s] is not ready to prepare due to %s", prepareCommand.getGlobalTransaction().prettyPrint(), prevTx); } return false; } } catch (InterruptedException e) { if (log.isTraceEnabled()) { log.tracef("[%s ]Interrupted while checking is the previous conflicting transactions has finished", prepareCommand.getGlobalTransaction().prettyPrint()); } return false; } } if (log.isTraceEnabled()) { log.tracef("[%s] is ready to prepare", prepareCommand.getGlobalTransaction().prettyPrint()); } return true; } /** * set the initialization of the thread before the validation ensures the validation order in conflicting * transactions * * @throws InterruptedException if this thread was interrupted */ protected void initializeValidation() throws Exception { String gtx = prepareCommand.getGlobalTransaction().prettyPrint(); //TODO is this really needed? invocationContextContainer.setContext(txInvocationContext); remoteTransaction.markForPreparing(); if (remoteTransaction.isMarkedForRollback()) { //this means that rollback has already been received transactionTable.removeRemoteTransaction(remoteTransaction.getGlobalTransaction()); throw new CacheException("Cannot prepare transaction" + gtx + ". it was already marked as rollback"); } if (remoteTransaction.isMarkedForCommit()) { log.tracef("Transaction %s marked for commit, skipping the write skew check and forcing 1PC", gtx); txInvocationContext.setFlags(Flag.SKIP_WRITE_SKEW_CHECK); prepareCommand.setOnePhaseCommit(true); } } /** * finishes the transaction, ie, mark the modification as applied and set the result (exception or not) invokes * the method {@link this.finishTransaction} if the transaction has the one phase commit set to true * * @param exception true if the result is an exception */ private void finishPrepare(boolean exception) { remoteTransaction.markPreparedAndNotify(); if (prepareCommand.isOnePhaseCommit()) { finishTransaction(remoteTransaction); transactionTable.removeRemoteTransaction(prepareCommand.getGlobalTransaction()); } else if (exception) { finishTransaction(remoteTransaction); //Note: I cannot remove from the remote table, otherwise, when the rollback arrives, it will create a // new remote transaction! } } } }