/* * 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.paging.impl; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.core.paging.PageTransactionInfo; import org.apache.activemq.artemis.core.paging.PagingManager; import org.apache.activemq.artemis.core.paging.cursor.PageIterator; import org.apache.activemq.artemis.core.paging.cursor.PagePosition; import org.apache.activemq.artemis.core.paging.cursor.PageSubscription; import org.apache.activemq.artemis.core.persistence.StorageManager; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.core.transaction.Transaction; import org.apache.activemq.artemis.core.transaction.TransactionOperationAbstract; import org.apache.activemq.artemis.core.transaction.TransactionPropertyIndexes; import org.apache.activemq.artemis.utils.DataConstants; import org.jboss.logging.Logger; public final class PageTransactionInfoImpl implements PageTransactionInfo { // Constants ----------------------------------------------------- // Attributes ---------------------------------------------------- private static final Logger logger = Logger.getLogger(PageTransactionInfoImpl.class); private long transactionID; private volatile long recordID = -1; private volatile boolean committed = false; private volatile boolean useRedelivery = false; private volatile boolean rolledback = false; private final AtomicInteger numberOfMessages = new AtomicInteger(0); private final AtomicInteger numberOfPersistentMessages = new AtomicInteger(0); private List<LateDelivery> lateDeliveries; // Static -------------------------------------------------------- // Constructors -------------------------------------------------- public PageTransactionInfoImpl(final long transactionID) { this(); this.transactionID = transactionID; } public PageTransactionInfoImpl() { } // Public -------------------------------------------------------- @Override public long getRecordID() { return recordID; } @Override public void setRecordID(final long recordID) { this.recordID = recordID; } @Override public long getTransactionID() { return transactionID; } @Override public void onUpdate(final int update, final StorageManager storageManager, PagingManager pagingManager) { int sizeAfterUpdate = numberOfMessages.addAndGet(-update); if (sizeAfterUpdate == 0 && storageManager != null) { try { storageManager.deletePageTransactional(this.recordID); } catch (Exception e) { ActiveMQServerLogger.LOGGER.pageTxDeleteError(e, recordID); } pagingManager.removeTransaction(this.transactionID); } } @Override public void increment(final int durableSize, final int nonDurableSize) { numberOfPersistentMessages.addAndGet(durableSize); numberOfMessages.addAndGet(durableSize + nonDurableSize); } @Override public int getNumberOfMessages() { return numberOfMessages.get(); } // EncodingSupport implementation @Override public synchronized void decode(final ActiveMQBuffer buffer) { transactionID = buffer.readLong(); numberOfMessages.set(buffer.readInt()); numberOfPersistentMessages.set(numberOfMessages.get()); committed = true; } @Override public synchronized void encode(final ActiveMQBuffer buffer) { buffer.writeLong(transactionID); buffer.writeInt(numberOfPersistentMessages.get()); } @Override public synchronized int getEncodeSize() { return DataConstants.SIZE_LONG + DataConstants.SIZE_INT; } @Override public synchronized void commit() { if (lateDeliveries != null) { // This is to make sure deliveries that were touched before the commit arrived will be delivered for (LateDelivery pos : lateDeliveries) { pos.getSubscription().redeliver(pos.getIterator(), pos.getPagePosition()); } lateDeliveries.clear(); } committed = true; lateDeliveries = null; } @Override public void store(final StorageManager storageManager, PagingManager pagingManager, final Transaction tx) throws Exception { storageManager.storePageTransaction(tx.getID(), this); } /* * This is to be used after paging. We will update the PageTransactions until they get all the messages delivered. On that case we will delete the page TX * (non-Javadoc) * @see org.apache.activemq.artemis.core.paging.PageTransactionInfo#storeUpdate(org.apache.activemq.artemis.core.persistence.StorageManager, org.apache.activemq.artemis.core.transaction.Transaction, int) */ @Override public void storeUpdate(final StorageManager storageManager, final PagingManager pagingManager, final Transaction tx) throws Exception { internalUpdatePageManager(storageManager, pagingManager, tx, 1); } @Override public void reloadUpdate(final StorageManager storageManager, final PagingManager pagingManager, final Transaction tx, final int increment) throws Exception { UpdatePageTXOperation updt = internalUpdatePageManager(storageManager, pagingManager, tx, increment); updt.setStored(); } /** * @param storageManager * @param pagingManager * @param tx */ protected UpdatePageTXOperation internalUpdatePageManager(final StorageManager storageManager, final PagingManager pagingManager, final Transaction tx, final int increment) { UpdatePageTXOperation pgtxUpdate = (UpdatePageTXOperation) tx.getProperty(TransactionPropertyIndexes.PAGE_TRANSACTION_UPDATE); if (pgtxUpdate == null) { pgtxUpdate = new UpdatePageTXOperation(storageManager, pagingManager); tx.putProperty(TransactionPropertyIndexes.PAGE_TRANSACTION_UPDATE, pgtxUpdate); tx.addOperation(pgtxUpdate); } tx.setContainsPersistent(); pgtxUpdate.addUpdate(this, increment); return pgtxUpdate; } @Override public boolean isCommit() { return committed; } @Override public void setCommitted(final boolean committed) { this.committed = committed; } @Override public boolean isRollback() { return rolledback; } @Override public synchronized void rollback() { rolledback = true; committed = false; if (lateDeliveries != null) { for (LateDelivery pos : lateDeliveries) { pos.getSubscription().lateDeliveryRollback(pos.getPagePosition()); } lateDeliveries = null; } } @Override public String toString() { return "PageTransactionInfoImpl(transactionID=" + transactionID + ",id=" + recordID + ",numberOfMessages=" + numberOfMessages + ")"; } @Override public synchronized boolean deliverAfterCommit(PageIterator iterator, PageSubscription cursor, PagePosition cursorPos) { if (logger.isTraceEnabled()) { logger.trace("deliver after commit on " + cursor + ", position=" + cursorPos); } if (committed && useRedelivery) { if (logger.isTraceEnabled()) { logger.trace("commit & useRedelivery on " + cursor + ", position=" + cursorPos); } cursor.addPendingDelivery(cursorPos); cursor.redeliver(iterator, cursorPos); return true; } else if (committed) { if (logger.isTraceEnabled()) { logger.trace("committed on " + cursor + ", position=" + cursorPos + ", ignoring position"); } return false; } else if (rolledback) { if (logger.isTraceEnabled()) { logger.trace("rolled back, position ignored on " + cursor + ", position=" + cursorPos); } cursor.positionIgnored(cursorPos); return true; } else { if (logger.isTraceEnabled()) { logger.trace("deliverAftercommit/else, marking useRedelivery on " + cursor + ", position " + cursorPos); } useRedelivery = true; if (lateDeliveries == null) { lateDeliveries = new LinkedList<>(); } cursor.addPendingDelivery(cursorPos); lateDeliveries.add(new LateDelivery(cursor, cursorPos, iterator)); return true; } } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- // Private ------------------------------------------------------- // Inner classes ------------------------------------------------- /** * a Message shouldn't be delivered until it's committed * For that reason the page-reference will be written right away * But in certain cases we can only deliver after the commit * For that reason we will perform a late delivery * through the method redeliver. */ private static class LateDelivery { final PageSubscription subscription; final PagePosition pagePosition; final PageIterator iterator; private LateDelivery(PageSubscription subscription, PagePosition pagePosition, PageIterator iterator) { this.subscription = subscription; this.pagePosition = pagePosition; this.iterator = iterator; } public PageSubscription getSubscription() { return subscription; } public PagePosition getPagePosition() { return pagePosition; } public PageIterator getIterator() { return iterator; } } private static class UpdatePageTXOperation extends TransactionOperationAbstract { private final HashMap<PageTransactionInfo, AtomicInteger> countsToUpdate = new HashMap<>(); private boolean stored = false; private final StorageManager storageManager; private final PagingManager pagingManager; private UpdatePageTXOperation(final StorageManager storageManager, final PagingManager pagingManager) { this.storageManager = storageManager; this.pagingManager = pagingManager; } public void setStored() { stored = true; } public void addUpdate(final PageTransactionInfo info, final int increment) { AtomicInteger counter = countsToUpdate.get(info); if (counter == null) { counter = new AtomicInteger(0); countsToUpdate.put(info, counter); } counter.addAndGet(increment); } @Override public void beforePrepare(Transaction tx) throws Exception { storeUpdates(tx); } @Override public void beforeCommit(Transaction tx) throws Exception { storeUpdates(tx); } @Override public void afterCommit(Transaction tx) { for (Map.Entry<PageTransactionInfo, AtomicInteger> entry : countsToUpdate.entrySet()) { entry.getKey().onUpdate(entry.getValue().intValue(), storageManager, pagingManager); } } private void storeUpdates(Transaction tx) throws Exception { if (!stored) { stored = true; for (Map.Entry<PageTransactionInfo, AtomicInteger> entry : countsToUpdate.entrySet()) { storageManager.updatePageTransaction(tx.getID(), entry.getKey(), entry.getValue().get()); } } } } }