/* * 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.interceptors.gmu; import org.infinispan.CacheException; import org.infinispan.commands.read.GetKeyValueCommand; import org.infinispan.commands.tx.CommitCommand; import org.infinispan.commands.tx.GMUCommitCommand; import org.infinispan.commands.tx.GMUPrepareCommand; import org.infinispan.commands.tx.PrepareCommand; import org.infinispan.commands.tx.RollbackCommand; import org.infinispan.commands.write.ApplyDeltaCommand; import org.infinispan.commands.write.ClearCommand; import org.infinispan.commands.write.PutKeyValueCommand; import org.infinispan.commands.write.RemoveCommand; import org.infinispan.commands.write.ReplaceCommand; import org.infinispan.commands.write.WriteCommand; import org.infinispan.container.DataContainer; import org.infinispan.container.entries.gmu.InternalGMUCacheEntry; import org.infinispan.container.versioning.EntryVersion; import org.infinispan.container.versioning.VersionGenerator; import org.infinispan.container.versioning.gmu.GMUVersionGenerator; import org.infinispan.context.InvocationContext; import org.infinispan.context.SingleKeyNonTxInvocationContext; import org.infinispan.context.impl.TxInvocationContext; import org.infinispan.factories.annotations.Inject; import org.infinispan.interceptors.EntryWrappingInterceptor; import org.infinispan.transaction.gmu.CommitLog; import org.infinispan.transaction.gmu.manager.TransactionCommitManager; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.jgroups.blocks.RequestHandler; import java.util.LinkedList; import java.util.List; import static org.infinispan.transaction.gmu.GMUHelper.*; /** * @author Pedro Ruivo * @since 5.2 */ public class GMUEntryWrappingInterceptor extends EntryWrappingInterceptor { private static final Log log = LogFactory.getLog(GMUEntryWrappingInterceptor.class); protected GMUVersionGenerator versionGenerator; private TransactionCommitManager transactionCommitManager; @Inject public void inject(TransactionCommitManager transactionCommitManager, DataContainer dataContainer, CommitLog commitLog, VersionGenerator versionGenerator) { this.transactionCommitManager = transactionCommitManager; this.versionGenerator = toGMUVersionGenerator(versionGenerator); } @Override public Object visitPrepareCommand(TxInvocationContext ctx, PrepareCommand command) throws Throwable { GMUPrepareCommand spc = convert(command, GMUPrepareCommand.class); if (ctx.isOriginLocal()) { spc.setVersion(ctx.getTransactionVersion()); spc.setReadSet(ctx.getReadSet()); } else { ctx.setTransactionVersion(spc.getPrepareVersion()); } wrapEntriesForPrepare(ctx, command); performValidation(ctx, spc); Object retVal = invokeNextInterceptor(ctx, command); if (ctx.isOriginLocal() && command.getModifications().length > 0) { EntryVersion commitVersion = calculateCommitVersion(ctx.getTransactionVersion(), versionGenerator, cll.getWriteOwners(ctx.getCacheTransaction())); ctx.setTransactionVersion(commitVersion); } else { retVal = ctx.getTransactionVersion(); } if (command.isOnePhaseCommit()) { commitContextEntries.commitContextEntries(ctx); } return retVal; } @Override public Object visitCommitCommand(TxInvocationContext ctx, CommitCommand command) throws Throwable { GMUCommitCommand gmuCommitCommand = convert(command, GMUCommitCommand.class); if (ctx.isOriginLocal()) { gmuCommitCommand.setCommitVersion(ctx.getTransactionVersion()); } else { ctx.setTransactionVersion(gmuCommitCommand.getCommitVersion()); } transactionCommitManager.commitTransaction(ctx.getCacheTransaction(), gmuCommitCommand.getCommitVersion()); Object retVal = null; try { retVal = invokeNextInterceptor(ctx, command); } catch (Throwable throwable) { //let ignore the exception. we cannot have some nodes applying the write set and another not another one //receives the rollback and don't applies the write set } finally { transactionCommitManager.awaitUntilCommitted(ctx.getCacheTransaction(), ctx.isOriginLocal() ? null : gmuCommitCommand); } return ctx.isOriginLocal() ? retVal : RequestHandler.DO_NOT_REPLY; } @Override public Object visitRollbackCommand(TxInvocationContext ctx, RollbackCommand command) throws Throwable { try { return invokeNextInterceptor(ctx, command); } finally { transactionCommitManager.rollbackTransaction(ctx.getCacheTransaction()); } } /* * NOTE: these are the only commands that passes values to the application and these keys needs to be validated * and added to the transaction read set. */ @Override public Object visitGetKeyValueCommand(InvocationContext ctx, GetKeyValueCommand command) throws Throwable { ctx.clearKeyReadInCommand(); Object retVal = super.visitGetKeyValueCommand(ctx, command); updateTransactionVersion(ctx); return retVal; } @Override public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable { ctx.clearKeyReadInCommand(); Object retVal = super.visitPutKeyValueCommand(ctx, command); updateTransactionVersion(ctx); return retVal; } @Override public Object visitRemoveCommand(InvocationContext ctx, RemoveCommand command) throws Throwable { ctx.clearKeyReadInCommand(); Object retVal = super.visitRemoveCommand(ctx, command); updateTransactionVersion(ctx); return retVal; } @Override public Object visitReplaceCommand(InvocationContext ctx, ReplaceCommand command) throws Throwable { ctx.clearKeyReadInCommand(); Object retVal = super.visitReplaceCommand(ctx, command); updateTransactionVersion(ctx); return retVal; } /** * validates the read set and returns the prepare version from the commit queue * * @param ctx the context * @param command the prepare command * @throws InterruptedException if interrupted */ protected void performValidation(TxInvocationContext ctx, GMUPrepareCommand command) throws InterruptedException { boolean hasToUpdateLocalKeys = hasLocalKeysToUpdate(command.getModifications()); boolean isReadOnly = command.getModifications().length == 0; if (!isReadOnly) { cll.performReadSetValidation(ctx, command); if (hasToUpdateLocalKeys) { transactionCommitManager.prepareTransaction(ctx.getCacheTransaction()); } else { transactionCommitManager.prepareReadOnlyTransaction(ctx.getCacheTransaction()); } } if (log.isDebugEnabled()) { log.debugf("Transaction %s can commit on this node. Prepare Version is %s", command.getGlobalTransaction().prettyPrint(), ctx.getTransactionVersion()); } } private void updateTransactionVersion(InvocationContext context) { if (!context.isInTxScope() && !context.isOriginLocal()) { return; } if (context instanceof SingleKeyNonTxInvocationContext) { if (log.isDebugEnabled()) { log.debugf("Received a SingleKeyNonTxInvocationContext... This should be a single read operation"); } return; } TxInvocationContext txInvocationContext = (TxInvocationContext) context; List<EntryVersion> entryVersionList = new LinkedList<EntryVersion>(); entryVersionList.add(txInvocationContext.getTransactionVersion()); if (log.isTraceEnabled()) { log.tracef("[%s] Keys read in this command: %s", txInvocationContext.getGlobalTransaction().prettyPrint(), txInvocationContext.getKeysReadInCommand()); } for (InternalGMUCacheEntry internalGMUCacheEntry : txInvocationContext.getKeysReadInCommand().values()) { Object key = internalGMUCacheEntry.getKey(); boolean local = cll.localNodeIsOwner(key); if (log.isTraceEnabled()) { log.tracef("[%s] Analyze entry [%s]: local?=%s", txInvocationContext.getGlobalTransaction().prettyPrint(), internalGMUCacheEntry, local); } if (txInvocationContext.hasModifications() && !internalGMUCacheEntry.isMostRecent()) { throw new CacheException("Read-Write transaction read an old value and should rollback"); } if (internalGMUCacheEntry.getMaximumTransactionVersion() != null) { entryVersionList.add(internalGMUCacheEntry.getMaximumTransactionVersion()); } txInvocationContext.getCacheTransaction().addReadKey(key); if (local) { txInvocationContext.setAlreadyReadOnThisNode(true); txInvocationContext.addReadFrom(cll.getAddress()); } } if (entryVersionList.size() > 1) { EntryVersion[] txVersionArray = new EntryVersion[entryVersionList.size()]; txInvocationContext.setTransactionVersion(versionGenerator.mergeAndMax(entryVersionList.toArray(txVersionArray))); } } private boolean hasLocalKeysToUpdate(WriteCommand[] modifications) { for (WriteCommand writeCommand : modifications) { if (writeCommand instanceof ClearCommand) { return true; } else if (writeCommand instanceof ApplyDeltaCommand) { if (cll.localNodeIsOwner(((ApplyDeltaCommand) writeCommand).getDeltaAwareKey())) { return true; } } else { for (Object key : writeCommand.getAffectedKeys()) { if (cll.localNodeIsOwner(key)) { return true; } } } } return false; } }