/** * 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.activemq.artemis.core.transaction.impl; import javax.transaction.xa.Xid; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.Pair; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.core.io.IOCallback; import org.apache.activemq.artemis.core.io.SequentialFile; import org.apache.activemq.artemis.core.journal.Journal; import org.apache.activemq.artemis.core.journal.JournalLoadInformation; import org.apache.activemq.artemis.core.paging.PageTransactionInfo; import org.apache.activemq.artemis.core.paging.PagedMessage; import org.apache.activemq.artemis.core.paging.PagingManager; import org.apache.activemq.artemis.core.paging.PagingStore; import org.apache.activemq.artemis.core.paging.cursor.PagePosition; import org.apache.activemq.artemis.core.persistence.AddressBindingInfo; import org.apache.activemq.artemis.core.persistence.GroupingInfo; import org.apache.activemq.artemis.core.persistence.OperationContext; import org.apache.activemq.artemis.core.persistence.QueueBindingInfo; import org.apache.activemq.artemis.core.persistence.QueueStatus; import org.apache.activemq.artemis.core.persistence.StorageManager; import org.apache.activemq.artemis.core.persistence.config.PersistedAddressSetting; import org.apache.activemq.artemis.core.persistence.config.PersistedRoles; import org.apache.activemq.artemis.core.persistence.impl.PageCountPending; import org.apache.activemq.artemis.core.postoffice.Binding; import org.apache.activemq.artemis.core.postoffice.PostOffice; import org.apache.activemq.artemis.core.replication.ReplicationManager; import org.apache.activemq.artemis.core.server.LargeServerMessage; import org.apache.activemq.artemis.core.server.MessageReference; import org.apache.activemq.artemis.core.server.RouteContextList; import org.apache.activemq.artemis.core.server.files.FileStoreMonitor; import org.apache.activemq.artemis.core.server.group.impl.GroupBinding; import org.apache.activemq.artemis.core.server.impl.AddressInfo; import org.apache.activemq.artemis.core.server.impl.JournalLoader; import org.apache.activemq.artemis.core.transaction.ResourceManager; import org.apache.activemq.artemis.core.transaction.Transaction; import org.apache.activemq.artemis.core.transaction.TransactionOperation; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.junit.Assert; import org.junit.Test; public class TransactionImplTest extends ActiveMQTestBase { @Test public void testTimeoutAndThenCommitWithARollback() throws Exception { TransactionImpl tx = new TransactionImpl(newXID(), new FakeSM(), 10); Assert.assertTrue(tx.hasTimedOut(System.currentTimeMillis() + 60000, 10)); final AtomicInteger commit = new AtomicInteger(0); final AtomicInteger rollback = new AtomicInteger(0); tx.addOperation(new TransactionOperation() { @Override public void beforePrepare(Transaction tx) throws Exception { } @Override public void afterPrepare(Transaction tx) { } @Override public void beforeCommit(Transaction tx) throws Exception { } @Override public void afterCommit(Transaction tx) { System.out.println("commit..."); commit.incrementAndGet(); } @Override public void beforeRollback(Transaction tx) throws Exception { } @Override public void afterRollback(Transaction tx) { System.out.println("rollback..."); rollback.incrementAndGet(); } @Override public List<MessageReference> getRelatedMessageReferences() { return null; } @Override public List<MessageReference> getListOnConsumer(long consumerID) { return null; } }); for (int i = 0; i < 2; i++) { try { tx.commit(); Assert.fail("Exception expected!"); } catch (ActiveMQException expected) { } } // it should just be ignored! tx.rollback(); Assert.assertEquals(0, commit.get()); Assert.assertEquals(1, rollback.get()); } @Test public void testTimeoutThenRollbackWithRollback() throws Exception { TransactionImpl tx = new TransactionImpl(newXID(), new FakeSM(), 10); Assert.assertTrue(tx.hasTimedOut(System.currentTimeMillis() + 60000, 10)); final AtomicInteger commit = new AtomicInteger(0); final AtomicInteger rollback = new AtomicInteger(0); tx.addOperation(new TransactionOperation() { @Override public void beforePrepare(Transaction tx) throws Exception { } @Override public void afterPrepare(Transaction tx) { } @Override public void beforeCommit(Transaction tx) throws Exception { } @Override public void afterCommit(Transaction tx) { System.out.println("commit..."); commit.incrementAndGet(); } @Override public void beforeRollback(Transaction tx) throws Exception { } @Override public void afterRollback(Transaction tx) { System.out.println("rollback..."); rollback.incrementAndGet(); } @Override public List<MessageReference> getRelatedMessageReferences() { return null; } @Override public List<MessageReference> getListOnConsumer(long consumerID) { return null; } }); tx.rollback(); // This is a case where another failure was detected (In parallel with the TX timeout for instance) tx.markAsRollbackOnly(new ActiveMQException("rollback only again")); tx.rollback(); Assert.assertEquals(0, commit.get()); Assert.assertEquals(1, rollback.get()); } class FakeSM implements StorageManager { @Override public OperationContext getContext() { return null; } @Override public void lineUpContext() { } @Override public void criticalError(Throwable error) { error.printStackTrace(); } @Override public OperationContext newContext(Executor executor) { return null; } @Override public OperationContext newSingleThreadContext() { return null; } @Override public void setContext(OperationContext context) { } @Override public void stop(boolean ioCriticalError, boolean sendFailover) throws Exception { } @Override public void pageClosed(SimpleString storeName, int pageNumber) { } @Override public void pageDeleted(SimpleString storeName, int pageNumber) { } @Override public long storeQueueStatus(long queueID, QueueStatus status) throws Exception { return 0; } @Override public void deleteQueueStatus(long recordID) throws Exception { } @Override public void pageWrite(PagedMessage message, int pageNumber) { } @Override public void afterCompleteOperations(IOCallback run) { run.done(); } @Override public void afterStoreOperations(IOCallback run) { run.done(); } @Override public boolean waitOnOperations(long timeout) throws Exception { return false; } @Override public void waitOnOperations() throws Exception { } @Override public void beforePageRead() throws Exception { } @Override public void afterPageRead() throws Exception { } @Override public ByteBuffer allocateDirectBuffer(int size) { return null; } @Override public void freeDirectBuffer(ByteBuffer buffer) { } @Override public void clearContext() { } @Override public void confirmPendingLargeMessageTX(Transaction transaction, long messageID, long recordID) throws Exception { } @Override public void injectMonitor(FileStoreMonitor monitor) throws Exception { } @Override public void confirmPendingLargeMessage(long recordID) throws Exception { } @Override public void storeMessage(Message message) throws Exception { } @Override public void storeReference(long queueID, long messageID, boolean last) throws Exception { } @Override public void deleteMessage(long messageID) throws Exception { } @Override public void storeAcknowledge(long queueID, long messageID) throws Exception { } @Override public void storeCursorAcknowledge(long queueID, PagePosition position) throws Exception { } @Override public void updateDeliveryCount(MessageReference ref) throws Exception { } @Override public void updateScheduledDeliveryTime(MessageReference ref) throws Exception { } @Override public void storeDuplicateID(SimpleString address, byte[] duplID, long recordID) throws Exception { } @Override public void deleteDuplicateID(long recordID) throws Exception { } @Override public void storeMessageTransactional(long txID, Message message) throws Exception { } @Override public void storeReferenceTransactional(long txID, long queueID, long messageID) throws Exception { } @Override public void storeAcknowledgeTransactional(long txID, long queueID, long messageID) throws Exception { } @Override public void storeCursorAcknowledgeTransactional(long txID, long queueID, PagePosition position) throws Exception { } @Override public void deleteCursorAcknowledgeTransactional(long txID, long ackID) throws Exception { } @Override public void deleteCursorAcknowledge(long ackID) throws Exception { } @Override public void storePageCompleteTransactional(long txID, long queueID, PagePosition position) throws Exception { } @Override public void deletePageComplete(long ackID) throws Exception { } @Override public void updateScheduledDeliveryTimeTransactional(long txID, MessageReference ref) throws Exception { } @Override public void storeDuplicateIDTransactional(long txID, SimpleString address, byte[] duplID, long recordID) throws Exception { } @Override public void updateDuplicateIDTransactional(long txID, SimpleString address, byte[] duplID, long recordID) throws Exception { } @Override public void deleteDuplicateIDTransactional(long txID, long recordID) throws Exception { } @Override public LargeServerMessage createLargeMessage() { return null; } @Override public LargeServerMessage createLargeMessage(long id, Message message) throws Exception { return null; } @Override public SequentialFile createFileForLargeMessage(long messageID, LargeMessageExtension extension) { return null; } @Override public void prepare(long txID, Xid xid) throws Exception { } @Override public void commit(long txID) throws Exception { } @Override public void commit(long txID, boolean lineUpContext) throws Exception { } @Override public void rollback(long txID) throws Exception { } @Override public void rollbackBindings(long txID) throws Exception { } @Override public void commitBindings(long txID) throws Exception { } @Override public void storePageTransaction(long txID, PageTransactionInfo pageTransaction) throws Exception { } @Override public void updatePageTransaction(long txID, PageTransactionInfo pageTransaction, int depage) throws Exception { } @Override public void deletePageTransactional(long recordID) throws Exception { } @Override public JournalLoadInformation loadMessageJournal(PostOffice postOffice, PagingManager pagingManager, ResourceManager resourceManager, Map<Long, QueueBindingInfo> queueInfos, Map<SimpleString, List<Pair<byte[], Long>>> duplicateIDMap, Set<Pair<Long, Long>> pendingLargeMessages, List<PageCountPending> pendingNonTXPageCounter, JournalLoader journalLoader) throws Exception { return null; } @Override public long storeHeuristicCompletion(Xid xid, boolean isCommit) throws Exception { return 0; } @Override public void deleteHeuristicCompletion(long id) throws Exception { } @Override public void addQueueBinding(long tx, Binding binding) throws Exception { } @Override public void deleteQueueBinding(long tx, long queueBindingID) throws Exception { } @Override public void addAddressBinding(long tx, AddressInfo addressInfo) throws Exception { } @Override public void deleteAddressBinding(long tx, long addressBindingID) throws Exception { } @Override public JournalLoadInformation loadBindingJournal(List<QueueBindingInfo> queueBindingInfos, List<GroupingInfo> groupingInfos, List<AddressBindingInfo> addressBindingInfos) throws Exception { return null; } @Override public void addGrouping(GroupBinding groupBinding) throws Exception { } @Override public void deleteGrouping(long tx, GroupBinding groupBinding) throws Exception { } @Override public void storeAddressSetting(PersistedAddressSetting addressSetting) throws Exception { } @Override public void deleteAddressSetting(SimpleString addressMatch) throws Exception { } @Override public List<PersistedAddressSetting> recoverAddressSettings() throws Exception { return null; } @Override public void storeSecurityRoles(PersistedRoles persistedRoles) throws Exception { } @Override public void deleteSecurityRoles(SimpleString addressMatch) throws Exception { } @Override public List<PersistedRoles> recoverPersistedRoles() throws Exception { return null; } @Override public long storePageCounter(long txID, long queueID, long value) throws Exception { return 0; } @Override public long storePendingCounter(long queueID, long pageID, int inc) throws Exception { return 0; } @Override public void deleteIncrementRecord(long txID, long recordID) throws Exception { } @Override public void deletePageCounter(long txID, long recordID) throws Exception { } @Override public void deletePendingPageCounter(long txID, long recordID) throws Exception { } @Override public long storePageCounterInc(long txID, long queueID, int add) throws Exception { return 0; } @Override public long storePageCounterInc(long queueID, int add) throws Exception { return 0; } @Override public Journal getBindingsJournal() { return null; } @Override public Journal getMessageJournal() { return null; } @Override public void startReplication(ReplicationManager replicationManager, PagingManager pagingManager, String nodeID, boolean autoFailBack, long initialReplicationSyncTimeout) throws Exception { } @Override public boolean addToPage(PagingStore store, Message msg, Transaction tx, RouteContextList listCtx) throws Exception { return false; } @Override public void stopReplication() { } @Override public void addBytesToLargeMessage(SequentialFile appendFile, long messageID, byte[] bytes) throws Exception { } @Override public void storeID(long journalID, long id) throws Exception { } @Override public void deleteID(long journalD) throws Exception { } @Override public void readLock() { } @Override public void readUnLock() { } @Override public void persistIdGenerator() { } @Override public void start() throws Exception { } @Override public void stop() throws Exception { } @Override public boolean isStarted() { return false; } @Override public long generateID() { return 0; } @Override public long getCurrentID() { return 0; } } }