/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.apache.geode.internal.cache; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.apache.logging.log4j.Logger; import org.apache.geode.CancelException; import org.apache.geode.DataSerializer; import org.apache.geode.cache.CommitIncompleteException; import org.apache.geode.cache.RegionDestroyedException; import org.apache.geode.cache.UnsupportedOperationInTransactionException; import org.apache.geode.distributed.DistributedMember; import org.apache.geode.distributed.internal.DM; import org.apache.geode.distributed.internal.DistributionManager; import org.apache.geode.distributed.internal.DistributionMessage; import org.apache.geode.distributed.internal.ReplyException; import org.apache.geode.distributed.internal.ReplyMessage; import org.apache.geode.distributed.internal.ReplyProcessor21; import org.apache.geode.distributed.internal.ReplySender; import org.apache.geode.distributed.internal.membership.InternalDistributedMember; import org.apache.geode.internal.Assert; import org.apache.geode.internal.DataSerializableFixedID; import org.apache.geode.internal.Version; import org.apache.geode.internal.cache.TXEntryState.DistTxThinEntryState; import org.apache.geode.internal.cache.locks.TXLockService; import org.apache.geode.internal.cache.tx.DistTxEntryEvent; import org.apache.geode.internal.i18n.LocalizedStrings; import org.apache.geode.internal.logging.LogService; import org.apache.geode.internal.logging.log4j.LogMarker; /** * */ public final class DistTXPrecommitMessage extends TXMessage { private static final Logger logger = LogService.getLogger(); ArrayList<DistTxEntryEvent> secondaryTransactionalOperations; /** for deserialization */ public DistTXPrecommitMessage() {} public DistTXPrecommitMessage(TXId txUniqId, InternalDistributedMember onBehalfOfClientMember, ReplyProcessor21 processor) { super(txUniqId.getUniqId(), onBehalfOfClientMember, processor); } @Override public int getDSFID() { return DISTTX_PRE_COMMIT_MESSAGE; } @Override protected boolean operateOnTx(TXId txId, DistributionManager dm) throws RemoteOperationException { GemFireCacheImpl cache = GemFireCacheImpl.getInstance(); TXManagerImpl txMgr = cache.getTXMgr(); if (logger.isDebugEnabled()) { logger.debug("DistTXPrecommitMessage.operateOnTx: Tx {} with Secondaries List {}", txId, this.secondaryTransactionalOperations); } // should not be commited before assert (!txMgr.isHostedTxRecentlyCompleted(txId)); // @see TXCommitMessage.process(DistributionManager) TXLockService.createDTLS(); // fix bug 38843; no-op if already created final TXStateProxy txStateProxy = txMgr.getTXState(); boolean precommitSuccess = true; TreeMap<String, ArrayList<DistTxThinEntryState>> entryStateSortedMap = new TreeMap<String, ArrayList<DistTxThinEntryState>>(); // [DISTTX] TODO - Test valid scenarios of null txState // if no TXState was created (e.g. due to only getEntry/size operations // that don't start remote TX) then ignore if (txStateProxy != null) { if (!txStateProxy.isDistTx() || !txStateProxy.isTxStateProxy() || txStateProxy.isCreatedOnDistTxCoordinator()) { throw new UnsupportedOperationInTransactionException( LocalizedStrings.DISTTX_TX_EXPECTED.toLocalizedString("DistTXStateProxyImplOnDatanode", txStateProxy.getClass().getSimpleName())); } ((DistTXStateProxyImplOnDatanode) txStateProxy).setPreCommitMessage(this); /* * Perform precommit * * [DISTTX] Handle different exceptions here */ txMgr.precommit(); precommitSuccess = ((DistTXStateProxyImplOnDatanode) txStateProxy).getPreCommitResponse(); if (precommitSuccess) { precommitSuccess = ((DistTXStateProxyImplOnDatanode) txStateProxy) .populateDistTxEntryStateList(entryStateSortedMap); if (!precommitSuccess) { entryStateSortedMap.clear(); if (logger.isDebugEnabled()) { logger.debug("DistTXPrecommitMessage.operateOnTx: Tx {} Failed while creating response", txId); } } } else { if (logger.isDebugEnabled()) { logger.debug( "DistTXPrecommitMessage.operateOnTx: Tx {} Failed while applying changes for replicates", txId); } } } // Send Response : Send false if conflict DistTxPrecommitResponse finalResponse = new DistTxPrecommitResponse(precommitSuccess, new ArrayList<ArrayList<DistTxThinEntryState>>(entryStateSortedMap.values())); DistTXPrecommitReplyMessage.send(getSender(), getProcessorId(), finalResponse, getReplySender(dm)); // return false so there isn't another reply return false; } @Override public void toData(DataOutput out) throws IOException { super.toData(out); DataSerializer.writeArrayList((ArrayList<?>) secondaryTransactionalOperations, out); } @Override public void fromData(DataInput in) throws IOException, ClassNotFoundException { super.fromData(in); this.secondaryTransactionalOperations = DataSerializer.readArrayList(in); } @Override public boolean isTransactionDistributed() { return true; } @Override public boolean canStartRemoteTransaction() { return true; } public ArrayList<DistTxEntryEvent> getSecondaryTransactionalOperations() { return secondaryTransactionalOperations; } public void setSecondaryTransactionalOperations( ArrayList<DistTxEntryEvent> secondaryTransactionalOperations) { this.secondaryTransactionalOperations = secondaryTransactionalOperations; } /** * This is the reply to a {@link DistTXPrecommitMessage}. */ public static final class DistTXPrecommitReplyMessage extends ReplyMessage { private transient DistTxPrecommitResponse commitResponse; /** * Empty constructor to conform to DataSerializable interface */ public DistTXPrecommitReplyMessage() {} public DistTXPrecommitReplyMessage(DataInput in) throws IOException, ClassNotFoundException { fromData(in); } private DistTXPrecommitReplyMessage(int processorId, DistTxPrecommitResponse val) { setProcessorId(processorId); this.commitResponse = val; } /** GetReplyMessages are always processed in-line */ @Override public boolean getInlineProcess() { return true; } /** * Return the value from the get operation, serialize it bytes as late as possible to avoid * making un-neccesary byte[] copies. De-serialize those same bytes as late as possible to avoid * using precious threads (aka P2P readers). * * @param recipient the origin VM that performed the get * @param processorId the processor on which the origin thread is waiting * @param val the raw value that will eventually be serialized * @param replySender distribution manager used to send the reply */ public static void send(InternalDistributedMember recipient, int processorId, DistTxPrecommitResponse val, ReplySender replySender) throws RemoteOperationException { Assert.assertTrue(recipient != null, "DistTXPhaseOneCommitReplyMessage NULL reply message"); DistTXPrecommitReplyMessage m = new DistTXPrecommitReplyMessage(processorId, val); m.setRecipient(recipient); replySender.putOutgoing(m); } /** * Processes this message. This method is invoked by the receiver of the message. * * @param dm the distribution manager that is processing the message. */ @Override public void process(final DM dm, ReplyProcessor21 processor) { final long startTime = getTimestamp(); if (logger.isTraceEnabled(LogMarker.DM)) { logger.trace(LogMarker.DM, "DistTXPhaseOneCommitReplyMessage process invoking reply processor with processorId:{}", this.processorId); } if (processor == null) { if (logger.isTraceEnabled(LogMarker.DM)) { logger.trace(LogMarker.DM, "DistTXPhaseOneCommitReplyMessage processor not found"); } return; } processor.process(this); } @Override public int getDSFID() { return DISTTX_PRE_COMMIT_REPLY_MESSAGE; } @Override public void toData(DataOutput out) throws IOException { super.toData(out); DataSerializer.writeObject(commitResponse, out); } @Override public void fromData(DataInput in) throws IOException, ClassNotFoundException { super.fromData(in); this.commitResponse = (DistTxPrecommitResponse) DataSerializer.readObject(in); } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("DistTXPhaseOneCommitReplyMessage").append("processorid=").append(this.processorId) .append(" reply to sender ").append(this.getSender()); return sb.toString(); } public DistTxPrecommitResponse getCommitResponse() { return commitResponse; } } /** * Reply processor which collects all CommitReplyExceptions for Dist Tx and emits a detailed * failure exception if problems occur * * @see TXCommitMessage.CommitReplyProcessor * * [DISTTX] TODO see if need ReliableReplyProcessor21? departed members? */ public static final class DistTxPrecommitReplyProcessor extends ReplyProcessor21 { private HashMap<DistributedMember, DistTXCoordinatorInterface> msgMap; private Map<DistributedMember, DistTxPrecommitResponse> commitResponseMap; private transient TXId txIdent = null; public DistTxPrecommitReplyProcessor(TXId txUniqId, DM dm, Set initMembers, HashMap<DistributedMember, DistTXCoordinatorInterface> msgMap) { super(dm, initMembers); this.msgMap = msgMap; // [DISTTX] TODO Do we need synchronised map? this.commitResponseMap = Collections.synchronizedMap(new HashMap<DistributedMember, DistTxPrecommitResponse>()); this.txIdent = txUniqId; } @Override public void process(DistributionMessage msg) { if (msg instanceof DistTXPrecommitReplyMessage) { DistTXPrecommitReplyMessage reply = (DistTXPrecommitReplyMessage) msg; this.commitResponseMap.put(reply.getSender(), reply.getCommitResponse()); } super.process(msg); } public void waitForPrecommitCompletion() { try { waitForRepliesUninterruptibly(); } catch (DistTxPrecommitExceptionCollectingException e) { e.handlePotentialCommitFailure(msgMap); } } @Override protected void processException(DistributionMessage msg, ReplyException ex) { if (msg instanceof ReplyMessage) { synchronized (this) { if (this.exception == null) { // Exception Container this.exception = new DistTxPrecommitExceptionCollectingException(txIdent); } DistTxPrecommitExceptionCollectingException cce = (DistTxPrecommitExceptionCollectingException) this.exception; if (ex instanceof CommitReplyException) { CommitReplyException cre = (CommitReplyException) ex; cce.addExceptionsFromMember(msg.getSender(), cre.getExceptions()); } else { cce.addExceptionsFromMember(msg.getSender(), Collections.singleton(ex)); } } } } @Override protected boolean stopBecauseOfExceptions() { return false; } public Set getCacheClosedMembers() { if (this.exception != null) { DistTxPrecommitExceptionCollectingException cce = (DistTxPrecommitExceptionCollectingException) this.exception; return cce.getCacheClosedMembers(); } else { return Collections.EMPTY_SET; } } public Set getRegionDestroyedMembers(String regionFullPath) { if (this.exception != null) { DistTxPrecommitExceptionCollectingException cce = (DistTxPrecommitExceptionCollectingException) this.exception; return cce.getRegionDestroyedMembers(regionFullPath); } else { return Collections.EMPTY_SET; } } public Map<DistributedMember, DistTxPrecommitResponse> getCommitResponseMap() { return commitResponseMap; } } /** * An Exception that collects many remote CommitExceptions * * @see TXCommitMessage.CommitExceptionCollectingException */ public static class DistTxPrecommitExceptionCollectingException extends ReplyException { private static final long serialVersionUID = -2681117727592137893L; /** Set of members that threw CacheClosedExceptions */ private final Set<InternalDistributedMember> cacheExceptions; /** key=region path, value=Set of members */ private final Map<String, Set<InternalDistributedMember>> regionExceptions; /** List of exceptions that were unexpected and caused the tx to fail */ private final Map fatalExceptions; private final TXId id; /* * [DISTTX] TODO Actually handle exceptions like commit conflict, primary bucket moved, etc */ public DistTxPrecommitExceptionCollectingException(TXId txIdent) { this.cacheExceptions = new HashSet<InternalDistributedMember>(); this.regionExceptions = new HashMap<String, Set<InternalDistributedMember>>(); this.fatalExceptions = new HashMap(); this.id = txIdent; } /** * Determine if the commit processing was incomplete, if so throw a detailed exception * indicating the source of the problem * * @param msgMap */ public void handlePotentialCommitFailure( HashMap<DistributedMember, DistTXCoordinatorInterface> msgMap) { if (fatalExceptions.size() > 0) { StringBuffer errorMessage = new StringBuffer("Incomplete commit of transaction ").append(id) .append(". Caused by the following exceptions: "); for (Iterator i = fatalExceptions.entrySet().iterator(); i.hasNext();) { Map.Entry me = (Map.Entry) i.next(); DistributedMember mem = (DistributedMember) me.getKey(); errorMessage.append(" From member: ").append(mem).append(" "); List exceptions = (List) me.getValue(); for (Iterator ei = exceptions.iterator(); ei.hasNext();) { Exception e = (Exception) ei.next(); errorMessage.append(e); for (StackTraceElement ste : e.getStackTrace()) { errorMessage.append("\n\tat ").append(ste); } if (ei.hasNext()) { errorMessage.append("\nAND\n"); } } errorMessage.append("."); } throw new CommitIncompleteException(errorMessage.toString()); } /* [DISTTX] TODO Not Sure if required */ // Mark any persistent members as offline // handleClosedMembers(msgMap); // handleRegionDestroyed(msgMap); } public Set<InternalDistributedMember> getCacheClosedMembers() { return this.cacheExceptions; } public Set getRegionDestroyedMembers(String regionFullPath) { Set members = (Set) this.regionExceptions.get(regionFullPath); if (members == null) { members = Collections.EMPTY_SET; } return members; } /** * Protected by (this) * * @param member * @param exceptions */ public void addExceptionsFromMember(InternalDistributedMember member, Set exceptions) { for (Iterator iter = exceptions.iterator(); iter.hasNext();) { Exception ex = (Exception) iter.next(); if (ex instanceof CancelException) { cacheExceptions.add(member); } else if (ex instanceof RegionDestroyedException) { String r = ((RegionDestroyedException) ex).getRegionFullPath(); Set<InternalDistributedMember> members = regionExceptions.get(r); if (members == null) { members = new HashSet(); regionExceptions.put(r, members); } members.add(member); } else { List el = (List) this.fatalExceptions.get(member); if (el == null) { el = new ArrayList(2); this.fatalExceptions.put(member, el); } el.add(ex); } } } } public static final class DistTxPrecommitResponse implements DataSerializableFixedID { private transient Boolean commitState; private transient ArrayList<ArrayList<DistTxThinEntryState>> distTxEventList; // Default constructor for serialisation public DistTxPrecommitResponse() {} public DistTxPrecommitResponse(boolean precommitSuccess, ArrayList<ArrayList<DistTxThinEntryState>> eventList) { this.commitState = precommitSuccess; this.distTxEventList = eventList; } @Override public Version[] getSerializationVersions() { return null; } @Override public int getDSFID() { return DIST_TX_PRE_COMMIT_RESPONSE; } @Override public void toData(DataOutput out) throws IOException { DataSerializer.writeBoolean(commitState, out); DataSerializer.writeArrayList(distTxEventList, out); } @Override public void fromData(DataInput in) throws IOException, ClassNotFoundException { this.commitState = DataSerializer.readBoolean(in); this.distTxEventList = DataSerializer.readArrayList(in); } public Boolean getCommitState() { return commitState; } public ArrayList<ArrayList<DistTxThinEntryState>> getDistTxEntryEventList() { return distTxEventList; } } }