/* * JBoss, Home of Professional Open Source * Copyright 2011 Red Hat Inc. 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 2.1 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; import org.infinispan.commands.write.WriteCommand; import org.infinispan.container.entries.CacheEntry; import org.infinispan.container.versioning.EntryVersionsMap; import org.infinispan.transaction.xa.GlobalTransaction; import org.infinispan.transaction.xa.InvalidTransactionException; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.LinkedList; import java.util.Map; /** * Defines the state of a remotely originated transaction. * * @author Mircea.Markus@jboss.com * @since 4.0 */ public class RemoteTransaction extends AbstractCacheTransaction implements Cloneable { private static final Log log = LogFactory.getLog(RemoteTransaction.class); private volatile boolean valid = true; /** * During state transfer only the locks are being migrated over, but no modifications. */ private boolean missingModifications; private EnumSet<State> state; //lazy initialization (e.g. 2PC does not need it) private TxDependencyLatch dependencyLatch; private enum State { /** * the prepare command was received and started the validation */ PREPARING, /** * the prepare command was received and finished the validation */ PREPARED, /** * the rollback command was received before the prepare command and the transaction must be aborted */ ROLLBACK_ONLY, /** * the commit command was received before the prepare command and the transaction must be committed */ COMMIT_ONLY } public RemoteTransaction(WriteCommand[] modifications, GlobalTransaction tx, int viewId) { super(tx, viewId); this.modifications = modifications == null || modifications.length == 0 ? Collections.<WriteCommand>emptyList() : Arrays.asList(modifications); lookedUpEntries = createMapEntries(this.modifications.size()); this.state = EnumSet.noneOf(State.class); } public RemoteTransaction(GlobalTransaction tx, int viewId) { super(tx, viewId); this.modifications = new LinkedList<WriteCommand>(); lookedUpEntries = createMapEntries(2); this.state = EnumSet.noneOf(State.class); } public void invalidate() { valid = false; } @Override public void putLookedUpEntry(Object key, CacheEntry e) { if (!valid) { throw new InvalidTransactionException("This remote transaction " + getGlobalTransaction() + " is invalid"); } else if (e == null) { return; //null value not allowed in concurrent hash map } if (log.isTraceEnabled()) { log.tracef("Adding key %s to tx %s", key, getGlobalTransaction()); } lookedUpEntries.put(key, e); } @Override public void putLookedUpEntries(Map<Object, CacheEntry> entries) { if (!valid) { throw new InvalidTransactionException("This remote transaction " + getGlobalTransaction() + " is invalid"); } if (log.isTraceEnabled()) { log.tracef("Adding keys %s to tx %s", entries.keySet(), getGlobalTransaction()); } lookedUpEntries.putAll(entries); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof RemoteTransaction)) return false; RemoteTransaction that = (RemoteTransaction) o; return tx.equals(that.tx); } @Override public int hashCode() { return tx.hashCode(); } @Override @SuppressWarnings("unchecked") public Object clone() { try { RemoteTransaction dolly = (RemoteTransaction) super.clone(); dolly.modifications = new ArrayList<WriteCommand>(modifications); dolly.lookedUpEntries = createMapEntries(lookedUpEntries); dolly.state.addAll(state); return dolly; } catch (CloneNotSupportedException e) { throw new IllegalStateException("Impossible!!"); } } @Override public String toString() { return "RemoteTransaction{" + "modifications=" + modifications + ", lookedUpEntries=" + lookedUpEntries + ", lockedKeys= " + lockedKeys + ", backupKeyLocks " + backupKeyLocks + ", isMissingModifications " + missingModifications + ", tx=" + tx + ", state=" + state + ", dependencyLatch=" + dependencyLatch + '}'; } public void setMissingModifications(boolean missingModifications) { this.missingModifications = missingModifications; } public boolean isMissingModifications() { return missingModifications; } /** * check if the transaction is marked for rollback (by the Rollback Command) * @return true if it is marked for rollback, false otherwise */ public synchronized boolean isMarkedForRollback() { return state.contains(State.ROLLBACK_ONLY); } /** * check if the transaction is marked for commit (by the Commit Command) * @return true if it is marked for commit, false otherwise */ public synchronized boolean isMarkedForCommit() { return state.contains(State.COMMIT_ONLY); } /** * mark the transaction as prepared (the validation was finished) and notify a possible pending commit or rollback * command */ public synchronized void markPreparedAndNotify() { state.add(State.PREPARED); this.notifyAll(); } /** * mark the transaction as preparing, blocking the commit and rollback commands until the * {@link #markPreparedAndNotify()} is invoked */ public synchronized void markForPreparing() { state.add(State.PREPARING); } /** * Commit and rollback commands invokes this method and they are blocked here if the state is PREPARING * * @param commit true if it is a commit command, false otherwise * @param newVersions the new versions in commit command (null for rollback) * @return true if the command needs to be processed, false otherwise * @throws InterruptedException when it is interrupted while waiting */ public final synchronized boolean waitPrepared(boolean commit, EntryVersionsMap newVersions) throws InterruptedException { setUpdatedEntryVersions(newVersions); boolean result; if (state.contains(State.PREPARED)) { result = true; log.tracef("Finished waiting: transaction already prepared"); } else if (state.contains(State.PREPARING)) { this.wait(); result = true; log.tracef("Transaction was in PREPARING state but now it is prepared"); } else { State status = commit ? State.COMMIT_ONLY : State.ROLLBACK_ONLY; log.tracef("Transaction hasn't received the prepare yet, setting status to: %s", status); state.add(status); result = false; } return result; } /** * mark the transactions as prepared and returns true if the second phase command was already delivered * * Note: used in {@link org.infinispan.reconfigurableprotocol.ReconfigurableProtocol} to abort and remove * transactions ASAP * * @return true if the 2nd phase commands was already deliver, false otherwise */ public final synchronized boolean check2ndPhaseAndPrepare() { state.add(State.PREPARED); return state.contains(State.ROLLBACK_ONLY) || state.contains(State.COMMIT_ONLY); } /** * returns the dependency latch for this transaction (lazy construction) * * @return the dependency latch for this transaction */ public synchronized final TxDependencyLatch getDependencyLatch() { if (dependencyLatch == null) { dependencyLatch = new TxDependencyLatch(getGlobalTransaction()); } return dependencyLatch; } }