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

org.infinispan.server.hotrod.TransactionRequestProcessor Maven / Gradle / Ivy

package org.infinispan.server.hotrod;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;

import javax.security.auth.Subject;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;

import org.infinispan.AdvancedCache;
import org.infinispan.commons.tx.XidImpl;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.IsolationLevel;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.container.versioning.VersionGenerator;
import org.infinispan.security.actions.SecurityActions;
import org.infinispan.server.hotrod.logging.Log;
import org.infinispan.server.hotrod.tx.PrepareCoordinator;
import org.infinispan.server.hotrod.tx.operation.CommitTransactionOperation;
import org.infinispan.server.hotrod.tx.operation.RollbackTransactionOperation;
import org.infinispan.server.hotrod.tx.table.GlobalTxTable;
import org.infinispan.server.hotrod.tx.table.TxState;
import org.infinispan.telemetry.InfinispanTelemetry;
import org.infinispan.transaction.tm.EmbeddedTransactionManager;
import org.infinispan.util.logging.LogFactory;

import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;

class TransactionRequestProcessor extends CacheRequestProcessor {
   private static final Log log = LogFactory.getLog(TransactionRequestProcessor.class, Log.class);

   TransactionRequestProcessor(Channel channel, Executor executor, HotRodServer server, InfinispanTelemetry telemetryService) {
      super(channel, executor, server, telemetryService);
   }

   private void writeTransactionResponse(HotRodHeader header, int value) {
      writeResponse(header, createTransactionResponse(header, value));
   }

   /**
    * Handles a rollback request from a client.
    */
   void rollbackTransaction(HotRodHeader header, Subject subject, XidImpl xid) {
      RollbackTransactionOperation operation = new RollbackTransactionOperation(header, server, subject, xid,
            this::writeTransactionResponse);
      executor.execute(operation);
   }

   /**
    * Handles a commit request from a client
    */
   void commitTransaction(HotRodHeader header, Subject subject, XidImpl xid) {
      CommitTransactionOperation operation = new CommitTransactionOperation(header, server, subject, xid,
            this::writeTransactionResponse);
      executor.execute(operation);
   }

   /**
    * Handles a prepare request from a client
    */
   void prepareTransaction(HotRodHeader header, Subject subject, XidImpl xid, boolean onePhaseCommit,
                           List writes, boolean recoverable, long timeout) {
      HotRodServer.ExtendedCacheInfo cacheInfo = server.getCacheInfo(header);
      AdvancedCache cache = server.cache(cacheInfo, header, subject);
      validateConfiguration(cache);
      executor.execute(() -> prepareTransactionInternal(header, cache, cacheInfo.versionGenerator, xid, onePhaseCommit,
            writes, recoverable, timeout));
   }

   void forgetTransaction(HotRodHeader header, Subject subject, XidImpl xid) {
      //TODO authentication?
      GlobalTxTable txTable = SecurityActions.getGlobalComponentRegistry(server.getCacheManager()).getComponent(GlobalTxTable.class);
      executor.execute(() -> {
         try {
            txTable.forgetTransaction(xid);
            writeSuccess(header);
         } catch (Throwable t) {
            writeException(header, t);
         }
      });
   }

   void getPreparedTransactions(HotRodHeader header, Subject subject) {
      //TODO authentication?
      if (log.isTraceEnabled()) {
         log.trace("Fetching transactions for recovery");
      }
      executor.execute(() -> {
         try {
            GlobalTxTable txTable = SecurityActions.getGlobalComponentRegistry(server.getCacheManager())
                  .getComponent(GlobalTxTable.class);
            Collection preparedTx = txTable.getPreparedTransactions();
            writeResponse(header, createRecoveryResponse(header, preparedTx));
         } catch (Throwable t) {
            writeException(header, t);
         }
      });
   }

   private void prepareTransactionInternal(HotRodHeader header, AdvancedCache cache,
                                           VersionGenerator versionGenerator, XidImpl xid,
                                           boolean onePhaseCommit, List writes,
                                           boolean recoverable, long timeout) {
      try {
         if (writes.isEmpty()) {
            //the client can optimize and avoid contacting the server when no data is written.
            if (log.isTraceEnabled()) {
               log.tracef("Transaction %s is read only.", xid);
            }
            writeResponse(header, createTransactionResponse(header, XAResource.XA_RDONLY));
            return;
         }
         PrepareCoordinator prepareCoordinator = new PrepareCoordinator(cache, xid, recoverable, timeout);

         if (checkExistingTxForPrepare(header, prepareCoordinator)) {
            if (log.isTraceEnabled()) {
               log.tracef("Transaction %s conflicts with another node.", xid);
            }
            return;
         }

         if (!prepareCoordinator.startTransaction()) {
            if (log.isTraceEnabled()) {
               log.tracef("Unable to start transaction %s", xid);
            }
            writeNotExecuted(header);
            return;
         }

         //forces the write-lock. used by pessimistic transaction. it ensures the key is not written after is it read and validated
         //optimistic transaction will use the write-skew check.
         AdvancedCache txCache = prepareCoordinator.decorateCache(cache);

         try {
            boolean rollback = false;
            for (TransactionWrite write : writes) {
               if (isValid(write, txCache)) {
                  if (write.isRemove()) {
                     txCache.remove(write.key);
                  } else {
                     write.metadata.version(versionGenerator.generateNew());
                     txCache.put(write.key, write.value, write.metadata.build());
                  }
               } else {
                  prepareCoordinator.setRollbackOnly();
                  rollback = true;
                  break;
               }
            }
            int xaCode = rollback ?
                         prepareCoordinator.rollback() :
                         prepareCoordinator.prepare(onePhaseCommit);
            writeResponse(header, createTransactionResponse(header, xaCode));
         } catch (Exception e) {
            writeResponse(header, createTransactionResponse(header, prepareCoordinator.rollback()));
         } finally {
            EmbeddedTransactionManager.dissociateTransaction();
         }
      } catch (Throwable t) {
         log.debugf(t, "Exception while replaying transaction %s for cache %s", xid, cache.getName());
         writeException(header, t);
      }
   }

   /**
    * Checks if the configuration (and the transaction manager) is able to handle client transactions.
    */
   private void validateConfiguration(AdvancedCache cache) {
      Configuration configuration = cache.getCacheConfiguration();
      if (!configuration.transaction().transactionMode().isTransactional()) {
         throw log.expectedTransactionalCache(cache.getName());
      }
      if (configuration.locking().lockIsolationLevel() != IsolationLevel.REPEATABLE_READ) {
         throw log.unexpectedIsolationLevel(cache.getName());
      }
   }

   /**
    * Checks if the transaction was already prepared in another node
    * 

* The client can send multiple requests to the server (in case of timeout or similar). This request is ignored when * (1) the originator is still alive; (2) the transaction is prepared or committed/rolled-back *

* If the transaction isn't prepared and the originator left the cluster, the previous transaction is rolled-back and * a new one is started. */ private boolean checkExistingTxForPrepare(HotRodHeader header, PrepareCoordinator txCoordinator) { TxState txState = txCoordinator.getTxState(); if (txState == null) { return false; } if (txCoordinator.isAlive(txState.getOriginator())) { //transaction started on another node but the node is still in the topology. 2 possible scenarios: // #1, the topology isn't updated // #2, the client timed-out waiting for the reply //in any case, we send a ignore reply and the client is free to retry (or rollback) writeNotExecuted(header); return true; } //originator is dead... //First phase state machine //success ACTIVE -> PREPARING -> PREPARED //failed ACTIVE -> MARK_ROLLBACK -> ROLLED_BACK or ACTIVE -> PREPARING -> ROLLED_BACK //1PC success ACTIVE -> PREPARING -> MARK_COMMIT -> COMMITTED switch (txState.getStatus()) { case ACTIVE: case PREPARING: //rollback existing transaction and retry with a new one txCoordinator.rollbackRemoteTransaction(txState.getGlobalTransaction()); return false; case PREPARED: case COMMITTED: //2PC since 1PC never reaches this state writeResponse(header, createTransactionResponse(header, XAResource.XA_OK)); return true; case MARK_ROLLBACK: //make sure it is rolled back and reply to the client txCoordinator.rollbackRemoteTransaction(txState.getGlobalTransaction()); case ROLLED_BACK: writeResponse(header, createTransactionResponse(header, XAException.XA_RBROLLBACK)); return true; case MARK_COMMIT: writeResponse(header, createTransactionResponse(header, txCoordinator.onePhaseCommitRemoteTransaction(txState.getGlobalTransaction(), txState.getModifications()))); return true; default: throw new IllegalStateException(); } } /** * Validates if the value read is still valid and the write operation can proceed. */ private boolean isValid(TransactionWrite write, AdvancedCache readCache) { if (write.skipRead()) { if (log.isTraceEnabled()) { log.tracef("Operation %s wasn't read.", write); } return true; } CacheEntry entry = readCache.getCacheEntry(write.key); if (write.wasNonExisting()) { if (log.isTraceEnabled()) { log.tracef("Key didn't exist for operation %s. Entry is %s", write, entry); } return entry == null || entry.getValue() == null; } if (log.isTraceEnabled()) { log.tracef("Checking version for operation %s. Entry is %s", write, entry); } return entry != null && write.versionRead == MetadataUtils.extractVersion(entry); } private ByteBuf createTransactionResponse(HotRodHeader header, int xaReturnCode) { return header.encoder().transactionResponse(header, server, channel, xaReturnCode); } private ByteBuf createRecoveryResponse(HotRodHeader header, Collection xids) { return header.encoder().recoveryResponse(header, server, channel, xids); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy