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

org.infinispan.hotrod.impl.transaction.SyncModeTransactionTable Maven / Gradle / Ivy

The newest version!
package org.infinispan.hotrod.impl.transaction;

import static org.infinispan.commons.tx.Util.transactionStatusToString;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.Consumer;
import java.util.function.Function;

import jakarta.transaction.RollbackException;
import jakarta.transaction.Status;
import jakarta.transaction.Synchronization;
import jakarta.transaction.SystemException;
import jakarta.transaction.Transaction;
import javax.transaction.xa.XAResource;

import org.infinispan.commons.CacheException;
import org.infinispan.commons.util.Util;
import org.infinispan.hotrod.impl.logging.Log;
import org.infinispan.hotrod.impl.logging.LogFactory;
import org.infinispan.hotrod.transaction.manager.RemoteXid;

/**
 * A {@link TransactionTable} that registers the cache as a {@link Synchronization} in the transaction.
 * 

* Only a single {@link Synchronization} is registered even if multiple caches interact with the same * transaction. *

* When more than one cache is involved in the {@link Transaction}, the prepare, commit and rollback * requests are sent sequential and they are ordered by the cache's name. *

* If a cache is read-only, the commit/rollback isn't invoked. * * @since 14.0 */ public class SyncModeTransactionTable extends AbstractTransactionTable { private static final Log log = LogFactory.getLog(SyncModeTransactionTable.class, Log.class); private final Map registeredTransactions = new ConcurrentHashMap<>(); private final UUID uuid = Util.threadLocalRandomUUID(); private final Consumer cleanup = registeredTransactions::remove; private final Function constructor = this::createSynchronizationAdapter; public SyncModeTransactionTable(long timeout) { super(timeout); } @Override public TransactionContext enlist(TransactionalRemoteCacheImpl txRemoteCache, Transaction tx) { assertStartedAndReturnFactory(); //registers a synchronization if it isn't done yet. SynchronizationAdapter adapter = registeredTransactions.computeIfAbsent(tx, constructor); //registers the cache. TransactionContext context = adapter.registerCache(txRemoteCache); if (log.isTraceEnabled()) { log.tracef("Xid=%s retrieving context: %s", adapter.xid, context); } return context; } /** * Creates and registers the {@link SynchronizationAdapter} in the {@link Transaction}. */ private SynchronizationAdapter createSynchronizationAdapter(Transaction transaction) { SynchronizationAdapter adapter = new SynchronizationAdapter(transaction, RemoteXid.create(uuid)); try { transaction.registerSynchronization(adapter); } catch (RollbackException | SystemException e) { throw new CacheException(e); } if (log.isTraceEnabled()) { log.tracef("Registered synchronization for transaction %s. Sync=%s", transaction, adapter); } return adapter; } private class SynchronizationAdapter implements Synchronization { private final Map> registeredCaches = new ConcurrentSkipListMap<>(); private final Transaction transaction; private final RemoteXid xid; private SynchronizationAdapter(Transaction transaction, RemoteXid xid) { this.transaction = transaction; this.xid = xid; } @Override public String toString() { return "SynchronizationAdapter{" + "registeredCaches=" + registeredCaches.keySet() + ", transaction=" + transaction + ", xid=" + xid + '}'; } @Override public void beforeCompletion() { if (log.isTraceEnabled()) { log.tracef("BeforeCompletion(xid=%s, remote-caches=%s)", xid, registeredCaches.keySet()); } if (isMarkedRollback()) { return; } for (TransactionContext txContext : registeredCaches.values()) { switch (txContext.prepareContext(xid, false, getTimeout())) { case XAResource.XA_OK: case XAResource.XA_RDONLY: break; //read only tx. case Integer.MIN_VALUE: //signals a marshaller error of key or value. the server wasn't contacted markAsRollback(); return; default: markAsRollback(); return; } } } @Override public void afterCompletion(int status) { if (log.isTraceEnabled()) { log.tracef("AfterCompletion(xid=%s, status=%s, remote-caches=%s)", xid, transactionStatusToString(status), registeredCaches.keySet()); } //the server commits everything when the first request arrives. try { boolean commit = status == Status.STATUS_COMMITTED; completeTransaction(xid, commit); } finally { forgetTransaction(xid); cleanup.accept(transaction); } } private void markAsRollback() { try { transaction.setRollbackOnly(); } catch (SystemException e) { log.debug("Exception in markAsRollback", e); } } private boolean isMarkedRollback() { try { return transaction.getStatus() == Status.STATUS_MARKED_ROLLBACK; } catch (SystemException e) { log.debug("Exception in isMarkedRollback", e); //lets assume not. return false; } } private TransactionContext registerCache(TransactionalRemoteCacheImpl txRemoteCache) { //noinspection unchecked return (TransactionContext) registeredCaches .computeIfAbsent(txRemoteCache.getName(), s -> createTxContext(txRemoteCache)); } private TransactionContext createTxContext(TransactionalRemoteCacheImpl remoteCache) { if (log.isTraceEnabled()) { log.tracef("Registering remote cache '%s' for transaction xid=%s", remoteCache.getName(), xid); } return new TransactionContext<>(remoteCache.keyMarshaller(), remoteCache.valueMarshaller(), remoteCache.getOperationsFactory(), remoteCache.getName(), false); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy