/* * 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.server.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.core.paging.cursor.NonExistentPage; import org.apache.activemq.artemis.core.persistence.StorageManager; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.core.server.MessageReference; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.core.transaction.Transaction; import org.apache.activemq.artemis.core.transaction.TransactionOperationAbstract; import org.apache.activemq.artemis.core.transaction.impl.TransactionImpl; import org.jboss.logging.Logger; public class RefsOperation extends TransactionOperationAbstract { private static final Logger logger = Logger.getLogger(RefsOperation.class); private final StorageManager storageManager; private Queue queue; List<MessageReference> refsToAck = new ArrayList<>(); List<MessageReference> pagedMessagesToPostACK = null; /** * It will ignore redelivery check, which is used during consumer.close * to not perform reschedule redelivery check */ protected boolean ignoreRedeliveryCheck = false; public RefsOperation(Queue queue, StorageManager storageManager) { this.queue = queue; this.storageManager = storageManager; } // once turned on, we shouldn't turn it off, that's why no parameters public void setIgnoreRedeliveryCheck() { ignoreRedeliveryCheck = true; } synchronized void addAck(final MessageReference ref) { refsToAck.add(ref); if (ref.isPaged()) { if (pagedMessagesToPostACK == null) { pagedMessagesToPostACK = new ArrayList<>(); } pagedMessagesToPostACK.add(ref); } } @Override public void afterRollback(final Transaction tx) { Map<QueueImpl, LinkedList<MessageReference>> queueMap = new HashMap<>(); long timeBase = System.currentTimeMillis(); //add any already acked refs, this means that they have been transferred via a producer.send() and have no // previous state persisted. List<MessageReference> ackedRefs = new ArrayList<>(); for (MessageReference ref : refsToAck) { ref.setConsumerId(null); if (logger.isTraceEnabled()) { logger.trace("rolling back " + ref); } try { if (ref.isAlreadyAcked()) { ackedRefs.add(ref); } rollbackRedelivery(tx, ref, timeBase, queueMap); } catch (Exception e) { ActiveMQServerLogger.LOGGER.errorCheckingDLQ(e); } } for (Map.Entry<QueueImpl, LinkedList<MessageReference>> entry : queueMap.entrySet()) { LinkedList<MessageReference> refs = entry.getValue(); QueueImpl queue = entry.getKey(); synchronized (queue) { queue.postRollback(refs); } } if (!ackedRefs.isEmpty()) { //since pre acked refs have no previous state we need to actually create this by storing the message and the //message references try { Transaction ackedTX = new TransactionImpl(storageManager); for (MessageReference ref : ackedRefs) { Message message = ref.getMessage(); if (message.isDurable()) { int durableRefCount = message.incrementDurableRefCount(); if (durableRefCount == 1) { storageManager.storeMessageTransactional(ackedTX.getID(), message); } Queue queue = ref.getQueue(); storageManager.storeReferenceTransactional(ackedTX.getID(), queue.getID(), message.getMessageID()); ackedTX.setContainsPersistent(); } message.incrementRefCount(); } ackedTX.commit(true); } catch (Exception e) { ActiveMQServerLogger.LOGGER.warn(e.getMessage(), e); } } } protected void rollbackRedelivery(Transaction tx, MessageReference ref, long timeBase, Map<QueueImpl, LinkedList<MessageReference>> queueMap) throws Exception { // if ignore redelivery check, we just perform redelivery straight if (ref.getQueue().checkRedelivery(ref, timeBase, ignoreRedeliveryCheck)) { LinkedList<MessageReference> toCancel = queueMap.get(ref.getQueue()); if (toCancel == null) { toCancel = new LinkedList<>(); queueMap.put((QueueImpl) ref.getQueue(), toCancel); } toCancel.addFirst(ref); } } @Override public void afterCommit(final Transaction tx) { for (MessageReference ref : refsToAck) { synchronized (ref.getQueue()) { queue.postAcknowledge(ref); } } if (pagedMessagesToPostACK != null) { for (MessageReference refmsg : pagedMessagesToPostACK) { decrementRefCount(refmsg); } } } private void decrementRefCount(MessageReference refmsg) { try { refmsg.getMessage().decrementRefCount(); } catch (NonExistentPage e) { // This could happen on after commit, since the page could be deleted on file earlier by another thread logger.debug(e); } catch (Exception e) { ActiveMQServerLogger.LOGGER.warn(e.getMessage(), e); } } @Override public synchronized List<MessageReference> getRelatedMessageReferences() { List<MessageReference> listRet = new LinkedList<>(); listRet.addAll(listRet); return listRet; } @Override public synchronized List<MessageReference> getListOnConsumer(long consumerID) { List<MessageReference> list = new LinkedList<>(); for (MessageReference ref : refsToAck) { if (ref.getConsumerId() != null && ref.getConsumerId().equals(consumerID)) { list.add(ref); } } return list; } public List<MessageReference> getReferencesToAcknowledge() { return refsToAck; } }