order
This paper mainly studies the ExpiryScanner of artemis
startExpiryScanner
activemq-artemis-2.11.0/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImpl.java
public class PostOfficeImpl implements PostOffice, NotificationListener, BindingsFactory { //...... private ExpiryReaper expiryReaperRunnable; //...... public synchronized void startExpiryScanner() { if (expiryReaperPeriod > 0) { if (expiryReaperRunnable != null) expiryReaperRunnable.stop(); expiryReaperRunnable = new ExpiryReaper(server.getScheduledPool(), server.getExecutorFactory().getExecutor(), expiryReaperPeriod, TimeUnit.MILLISECONDS, false); expiryReaperRunnable.start(); } } public synchronized void stop() throws Exception { started = false; managementService.removeNotificationListener(this); if (expiryReaperRunnable != null) expiryReaperRunnable.stop(); if (addressQueueReaperRunnable != null) addressQueueReaperRunnable.stop(); addressManager.clear(); queueInfos.clear(); } //...... }
- PostOfficeImpl's startExpiryScanner method executes expiryReaperRunnable.start(); its stop method executes expiryReaperRunnable.stop()
ExpiryReaper
activemq-artemis-2.11.0/artemis-server/src/main/java/org/apache/activemq/artemis/core/postoffice/impl/PostOfficeImpl.java
private final class ExpiryReaper extends ActiveMQScheduledComponent { ExpiryReaper(ScheduledExecutorService scheduledExecutorService, Executor executor, long checkPeriod, TimeUnit timeUnit, boolean onDemand) { super(scheduledExecutorService, executor, checkPeriod, timeUnit, onDemand); } @Override public void run() { // The reaper thread should be finished case the PostOffice is gone // This is to avoid leaks on PostOffice between stops and starts for (Queue queue : getLocalQueues()) { try { queue.expireReferences(); } catch (Exception e) { ActiveMQServerLogger.LOGGER.errorExpiringMessages(e); } } } }
- ExpiryReaper inherits ActiveMQScheduledComponent, and its run method will traverse localQueue and execute queue. Expirreferences() one by one
expireReferences
activemq-artemis-2.11.0/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/QueueImpl.java
public class QueueImpl extends CriticalComponentImpl implements Queue { //...... private final ExpiryScanner expiryScanner = new ExpiryScanner(); //...... public void expireReferences() { if (isExpirationRedundant()) { return; } if (!queueDestroyed && expiryScanner.scannerRunning.get() == 0) { expiryScanner.scannerRunning.incrementAndGet(); getExecutor().execute(expiryScanner); } } //...... }
- The expirreferences method of QueueImpl will submit to the executor to execute expiryScanner
ExpiryScanner
activemq-artemis-2.11.0/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/QueueImpl.java
class ExpiryScanner implements Runnable { public AtomicInteger scannerRunning = new AtomicInteger(0); @Override public void run() { boolean expired = false; boolean hasElements = false; int elementsExpired = 0; LinkedList<MessageReference> expiredMessages = new LinkedList<>(); synchronized (QueueImpl.this) { if (queueDestroyed) { return; } if (logger.isDebugEnabled()) { logger.debug("Scanning for expires on " + QueueImpl.this.getName()); } LinkedListIterator<MessageReference> iter = iterator(); try { while (postOffice.isStarted() && iter.hasNext()) { hasElements = true; MessageReference ref = iter.next(); if (ref.getMessage().isExpired()) { incDelivering(ref); expired = true; expiredMessages.add(ref); iter.remove(); if (++elementsExpired >= MAX_DELIVERIES_IN_LOOP) { logger.debug("Breaking loop of expiring"); scannerRunning.incrementAndGet(); getExecutor().execute(this); break; } } } } finally { try { iter.close(); } catch (Throwable ignored) { } scannerRunning.decrementAndGet(); logger.debug("Scanning for expires on " + QueueImpl.this.getName() + " done"); } } if (!expiredMessages.isEmpty()) { Transaction tx = new TransactionImpl(storageManager); for (MessageReference ref : expiredMessages) { if (tx == null) { tx = new TransactionImpl(storageManager); } try { expire(tx, ref); refRemoved(ref); } catch (Exception e) { ActiveMQServerLogger.LOGGER.errorExpiringReferencesOnQueue(e, ref); } } try { tx.commit(); } catch (Exception e) { ActiveMQServerLogger.LOGGER.unableToCommitTransaction(e); } logger.debug("Expired " + elementsExpired + " references"); } // If empty we need to schedule depaging to make sure we would depage expired messages as well if ((!hasElements || expired) && pageIterator != null && pageIterator.tryNext() > 0) { scheduleDepage(true); } } }
- ExpiryScanner implements the Runnable interface, and its run method will traverse the iterator of MessageReference, judge whether the message is expired one by one, if it is true, execute increasing and add to and remove from the iterator, and then increase expiredMessages and judge whether it is greater than or equal to MAX_DELIVERIES_IN_LOOP(1000). If it is true, it will be submitted to the executor for execution and jump out Loop; then traverse expiredMessages, execute expire(tx, ref) and refRemoved(ref) one by one, and finally execute tx.commit()
isExpired
activemq-artemis-2.11.0/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/Message.java
public interface Message { //...... default boolean isExpired() { if (getExpiration() == 0) { return false; } return System.currentTimeMillis() - getExpiration() >= 0; } //...... }
- The isExpired method of Message determines whether the expiration is 0. If it is 0, it returns false. Otherwise, it determines whether the difference between the current time and the expiration is greater than or equal to 0. If it is true, it returns true
expire
activemq-artemis-2.11.0/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/QueueImpl.java
public class QueueImpl extends CriticalComponentImpl implements Queue { //...... private void expire(final Transaction tx, final MessageReference ref) throws Exception { SimpleString expiryAddress = addressSettingsRepository.getMatch(address.toString()).getExpiryAddress(); if (expiryAddress != null) { Bindings bindingList = postOffice.lookupBindingsForAddress(expiryAddress); if (bindingList == null || bindingList.getBindings().isEmpty()) { ActiveMQServerLogger.LOGGER.errorExpiringReferencesNoBindings(expiryAddress); acknowledge(tx, ref, AckReason.EXPIRED, null); } else { move(expiryAddress, tx, ref, true, true); } } else { if (!printErrorExpiring) { printErrorExpiring = true; // print this only once ActiveMQServerLogger.LOGGER.errorExpiringReferencesNoQueue(name); } acknowledge(tx, ref, AckReason.EXPIRED, null); } if (server != null && server.hasBrokerMessagePlugins()) { ExpiryLogger expiryLogger = (ExpiryLogger)tx.getProperty(TransactionPropertyIndexes.EXPIRY_LOGGER); if (expiryLogger == null) { expiryLogger = new ExpiryLogger(); tx.putProperty(TransactionPropertyIndexes.EXPIRY_LOGGER, expiryLogger); tx.addOperation(expiryLogger); } expiryLogger.addExpiry(address, ref); } } //...... }
- The expire method first obtains the expiryAddress, and then obtains the bindingList through postOffice.lookupBindingsForAddress(expiryAddress). If it is not null, the move operation is performed
move
activemq-artemis-2.11.0/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/QueueImpl.java
public class QueueImpl extends CriticalComponentImpl implements Queue { //...... private void move(final SimpleString toAddress, final Transaction tx, final MessageReference ref, final boolean expiry, final boolean rejectDuplicate, final long... queueIDs) throws Exception { Message copyMessage = makeCopy(ref, expiry); copyMessage.setAddress(toAddress); if (queueIDs != null && queueIDs.length > 0) { ByteBuffer buffer = ByteBuffer.allocate(8 * queueIDs.length); for (long id : queueIDs) { buffer.putLong(id); } copyMessage.putBytesProperty(Message.HDR_ROUTE_TO_IDS.toString(), buffer.array()); } postOffice.route(copyMessage, tx, false, rejectDuplicate); if (expiry) { acknowledge(tx, ref, AckReason.EXPIRED, null); } else { acknowledge(tx, ref); } } //...... }
- The move method uses makeCopy to copy the copyMessage, and then reroutes it through postOffice.route(copyMessage, tx, false, rejectDuplicate). Finally, it executes the acknowledge method. If the expiry is true, the reason is AckReason.EXPIRED
Summary
- ExpiryScanner implements the Runnable interface, and its run method will traverse the iterator of MessageReference, judge whether the message is expired one by one, and if it is true, execute incDelivering and add it to and remove it from the iterator; then traverse expiredMessages, execute expire(tx, ref) and refRemoved(ref) one by one, and finally execute tx.commit()
- The expire method first obtains the expiryAddress, and then obtains the bindingList through postOffice.lookupBindingsForAddress(expiryAddress). If it is not null, perform the move operation
- The move method uses makeCopy to copy the copyMessage, and then reroutes it through postOffice.route(copyMessage, tx, false, rejectDuplicate). Finally, it executes the acknowledge method. If the expiry is true, the reason is AckReason.EXPIRED