Talk about the push-pull mode of artemis message

Posted by raker7 on Fri, 24 Jan 2020 15:31:01 +0100

order

This paper mainly studies the push-pull mode of artemis messages

Pull mode

receive

activemq-artemis-2.11.0/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/ActiveMQMessageConsumer.java

public final class ActiveMQMessageConsumer implements QueueReceiver, TopicSubscriber {

   //......

   @Override
   public Message receive() throws JMSException {
      return getMessage(0, false);
   }

   @Override
   public Message receive(final long timeout) throws JMSException {
      return getMessage(timeout, false);
   }

   @Override
   public Message receiveNoWait() throws JMSException {
      return getMessage(0, true);
   }

   private ActiveMQMessage getMessage(final long timeout, final boolean noWait) throws JMSException {
      try {
         ClientMessage coreMessage;

         if (noWait) {
            coreMessage = consumer.receiveImmediate();
         } else {
            coreMessage = consumer.receive(timeout);
         }

         ActiveMQMessage jmsMsg = null;

         if (coreMessage != null) {
            ClientSession coreSession = session.getCoreSession();
            boolean needSession = ackMode == Session.CLIENT_ACKNOWLEDGE ||
               ackMode == ActiveMQJMSConstants.INDIVIDUAL_ACKNOWLEDGE ||
               coreMessage.getType() == ActiveMQObjectMessage.TYPE;

            if (coreMessage.getRoutingType() == null) {
               coreMessage.setRoutingType(destination.isQueue() ? RoutingType.ANYCAST : RoutingType.MULTICAST);
            }
            if (session.isEnable1xPrefixes()) {
               jmsMsg = ActiveMQCompatibleMessage.createMessage(coreMessage, needSession ? coreSession : null, options);
            } else {
               jmsMsg = ActiveMQMessage.createMessage(coreMessage, needSession ? coreSession : null, options);
            }

            try {
               jmsMsg.doBeforeReceive();
            } catch (IndexOutOfBoundsException ioob) {
               ((ClientSessionInternal) session.getCoreSession()).markRollbackOnly();
               // In case this exception happen you will need to know where it happened.
               // it has been a bug here in the past, and this was used to debug it.
               // nothing better than keep it for future investigations in case it happened again
               IndexOutOfBoundsException newIOOB = new IndexOutOfBoundsException(ioob.getMessage() + "@" + jmsMsg.getCoreMessage());
               newIOOB.initCause(ioob);
               ActiveMQClientLogger.LOGGER.unableToGetMessage(newIOOB);
               throw ioob;
            }

            // We Do the ack after doBeforeReceive, as in the case of large messages, this may fail so we don't want messages redelivered
            // https://issues.jboss.org/browse/JBPAPP-6110
            if (session.getAcknowledgeMode() == ActiveMQJMSConstants.INDIVIDUAL_ACKNOWLEDGE) {
               jmsMsg.setIndividualAcknowledge();
            } else if (session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE) {
               jmsMsg.setClientAcknowledge();
               coreMessage.acknowledge();
            } else {
               coreMessage.acknowledge();
            }
         }

         return jmsMsg;
      } catch (ActiveMQException e) {
         ((ClientSessionInternal) session.getCoreSession()).markRollbackOnly();
         throw JMSExceptionHelper.convertFromActiveMQException(e);
      } catch (ActiveMQInterruptedException e) {
         ((ClientSessionInternal) session.getCoreSession()).markRollbackOnly();
         throw JMSExceptionHelper.convertFromActiveMQException(e);
      }
   }

   //......
}
  • The receive method of ActiveMQMessageConsumer finally calls the getMessage method, which performs jmsMsg.setIndividualAcknowledge() for session.getAcknowledgeMode(), and coreMessage.acknowledge() for the rest.

acknowledge

activemq-artemis-2.11.0/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientMessageImpl.java

public class ClientMessageImpl extends CoreMessage implements ClientMessageInternal {

   //......

   public ClientMessageImpl acknowledge() throws ActiveMQException {
      if (consumer != null) {
         consumer.acknowledge(this);
      }

      return this;
   }

   //......
}
  • The acknowledge method of ClientMessageImpl executes consumer.acknowledge(this)

Push mode

handleMessage

activemq-artemis-2.11.0/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientConsumerImpl.java

public final class ClientConsumerImpl implements ClientConsumerInternal {

   //......

   public synchronized void handleMessage(final ClientMessageInternal message) throws Exception {
      if (closing) {
         // This is ok - we just ignore the message
         return;
      }

      if (message.getBooleanProperty(Message.HDR_LARGE_COMPRESSED)) {
         handleCompressedMessage(message);
      } else {
         handleRegularMessage(message);
      }
   }

   private void handleRegularMessage(ClientMessageInternal message) {
      if (message.getAddress() == null) {
         message.setAddress(queueInfo.getAddress());
      }

      message.onReceipt(this);

      if (!ackIndividually && message.getPriority() != 4 && !message.containsProperty(ClientConsumerImpl.FORCED_DELIVERY_MESSAGE)) {
         // We have messages of different priorities so we need to ack them individually since the order
         // of them in the ServerConsumerImpl delivery list might not be the same as the order they are
         // consumed in, which means that acking all up to won't work
         ackIndividually = true;
      }

      // Add it to the buffer
      buffer.addTail(message, message.getPriority());

      if (handler != null) {
         // Execute using executor
         if (!stopped) {
            queueExecutor();
         }
      } else {
         notify();
      }
   }

   private void queueExecutor() {
      if (logger.isTraceEnabled()) {
         logger.trace(this + "::Adding Runner on Executor for delivery");
      }

      sessionExecutor.execute(runner);
   }

   //......

}
  • handleRegularMessage of ClientConsumerImpl will execute buffer.addTail(message, message.getPriority()) and queueExecutor(), which will execute Runner through sessionexecution

callOnMessage

activemq-artemis-2.11.0/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientConsumerImpl.java

public final class ClientConsumerImpl implements ClientConsumerInternal {

   //......

   private final PriorityLinkedList<ClientMessageInternal> buffer = new PriorityLinkedListImpl<>(ClientConsumerImpl.NUM_PRIORITIES);

   //......   

   private class Runner implements Runnable {

      @Override
      public void run() {
         try {
            callOnMessage();
         } catch (Exception e) {
            ActiveMQClientLogger.LOGGER.onMessageError(e);

            lastException = e;
         }
      }
   }

   private void callOnMessage() throws Exception {
      if (closing || stopped) {
         return;
      }

      session.workDone();

      // We pull the message from the buffer from inside the Runnable so we can ensure priority
      // ordering. If we just added a Runnable with the message to the executor immediately as we get it
      // we could not do that

      ClientMessageInternal message;

      // Must store handler in local variable since might get set to null
      // otherwise while this is executing and give NPE when calling onMessage
      MessageHandler theHandler = handler;

      if (theHandler != null) {
         if (rateLimiter != null) {
            rateLimiter.limit();
         }

         failedOver = false;

         synchronized (this) {
            message = buffer.poll();
         }

         if (message != null) {
            if (message.containsProperty(ClientConsumerImpl.FORCED_DELIVERY_MESSAGE)) {
               //Ignore, this could be a relic from a previous receiveImmediate();
               return;
            }

            boolean expired = message.isExpired();

            flowControlBeforeConsumption(message);

            if (!expired) {
               if (logger.isTraceEnabled()) {
                  logger.trace(this + "::Calling handler.onMessage");
               }
               final ClassLoader originalLoader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
                  @Override
                  public ClassLoader run() {
                     ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();

                     Thread.currentThread().setContextClassLoader(contextClassLoader);

                     return originalLoader;
                  }
               });

               onMessageThread = Thread.currentThread();
               try {
                  theHandler.onMessage(message);
               } finally {
                  try {
                     AccessController.doPrivileged(new PrivilegedAction<Object>() {
                        @Override
                        public Object run() {
                           Thread.currentThread().setContextClassLoader(originalLoader);
                           return null;
                        }
                     });
                  } catch (Exception e) {
                     ActiveMQClientLogger.LOGGER.failedPerformPostActionsOnMessage(e);
                  }

                  onMessageThread = null;
               }

               if (logger.isTraceEnabled()) {
                  logger.trace(this + "::Handler.onMessage done");
               }

               if (message.isLargeMessage()) {
                  message.discardBody();
               }
            } else {
               session.expire(this, message);
            }

            // If slow consumer, we need to send 1 credit to make sure we get another message
            if (clientWindowSize == 0) {
               startSlowConsumer();
            }
         }
      }
   }

   //......
}   
  • The Runner's run method will execute the callOnMessage method, which will execute the handler. Onmessage (message) from the buffer.poll() message if it is not null and not expired

onMessage

activemq-artemis-2.11.0/artemis-jms-client/src/main/java/org/apache/activemq/artemis/jms/client/JMSMessageListenerWrapper.java

public class JMSMessageListenerWrapper implements MessageHandler {

   private final ConnectionFactoryOptions options;
   private final ActiveMQConnection connection;

   private final ActiveMQSession session;

   private final MessageListener listener;

   private final ClientConsumer consumer;

   private final boolean transactedOrClientAck;

   private final boolean individualACK;

   private final boolean clientACK;

   protected JMSMessageListenerWrapper(final ConnectionFactoryOptions options,
                                       final ActiveMQConnection connection,
                                       final ActiveMQSession session,
                                       final ClientConsumer consumer,
                                       final MessageListener listener,
                                       final int ackMode) {
      this.options = options;

      this.connection = connection;

      this.session = session;

      this.consumer = consumer;

      this.listener = listener;

      transactedOrClientAck = (ackMode == Session.SESSION_TRANSACTED || ackMode == Session.CLIENT_ACKNOWLEDGE) || session.isXA();

      individualACK = (ackMode == ActiveMQJMSConstants.INDIVIDUAL_ACKNOWLEDGE);

      clientACK = (ackMode == Session.CLIENT_ACKNOWLEDGE);
   }

   /**
    * In this method we apply the JMS acknowledgement and redelivery semantics
    * as per JMS spec
    */
   @Override
   public void onMessage(final ClientMessage message) {
      ActiveMQMessage msg;

      if (session.isEnable1xPrefixes()) {
         msg = ActiveMQCompatibleMessage.createMessage(message, session.getCoreSession(), options);
      } else {
         msg = ActiveMQMessage.createMessage(message, session.getCoreSession(), options);
      }

      if (individualACK) {
         msg.setIndividualAcknowledge();
      }

      if (clientACK) {
         msg.setClientAcknowledge();
      }

      try {
         msg.doBeforeReceive();
      } catch (Exception e) {
         ActiveMQJMSClientLogger.LOGGER.errorPreparingMessageForReceipt(msg.getCoreMessage().toString(), e);
         return;
      }

      if (transactedOrClientAck) {
         try {
            message.acknowledge();
         } catch (ActiveMQException e) {
            ((ClientSessionInternal) session.getCoreSession()).markRollbackOnly();
            ActiveMQJMSClientLogger.LOGGER.errorProcessingMessage(e);
         }
      }

      try {
         connection.getThreadAwareContext().setCurrentThread(false);
         listener.onMessage(msg);
      } catch (RuntimeException e) {
         // See JMS 1.1 spec, section 4.5.2

         ActiveMQJMSClientLogger.LOGGER.onMessageError(e);

         if (!transactedOrClientAck) {
            try {
               if (individualACK) {
                  message.individualAcknowledge();
               }

               session.getCoreSession().rollback(true);

               session.setRecoverCalled(true);
            } catch (Exception e2) {
               ActiveMQJMSClientLogger.LOGGER.errorRecoveringSession(e2);
            }
         }
      } finally {
         connection.getThreadAwareContext().clearCurrentThread(false);
      }
      if (!session.isRecoverCalled() && !individualACK) {
         try {
            // We don't want to call this if the consumer was closed from inside onMessage
            if (!consumer.isClosed() && !transactedOrClientAck) {
               message.acknowledge();
            }
         } catch (ActiveMQException e) {
            ((ClientSessionInternal) session.getCoreSession()).markRollbackOnly();
            ActiveMQJMSClientLogger.LOGGER.errorProcessingMessage(e);
         }
      }

      session.setRecoverCalled(false);
   }
}
  • The onMessage method will execute message.acknowledge() when the transactedOrClientAck is true, and message.acknowledge() when the listener.onMessage(msg) is triggered when it is not session.isRecoverCalled(), is not individualACK, is not consumer.isClosed(), and is not transactedOrClientAck

acknowledge

activemq-artemis-2.11.0/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientMessageImpl.java

public class ClientMessageImpl extends CoreMessage implements ClientMessageInternal {
	
	//......

   public ClientMessageImpl acknowledge() throws ActiveMQException {
      if (consumer != null) {
         consumer.acknowledge(this);
      }

      return this;
   }

	//......

}
  • The acknowledge method executes the consumer.acknowledge(this) method

ClientConsumerImpl.acknowledge

activemq-artemis-2.11.0/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientConsumerImpl.java

public final class ClientConsumerImpl implements ClientConsumerInternal {

   //......

   public void acknowledge(final ClientMessage message) throws ActiveMQException {
      ClientMessageInternal cmi = (ClientMessageInternal) message;

      if (ackIndividually) {
         individualAcknowledge(message);
      } else {

         ackBytes += message.getEncodeSize();

         if (logger.isTraceEnabled()) {
            logger.trace(this + "::acknowledge ackBytes=" + ackBytes + " and ackBatchSize=" + ackBatchSize + ", encodeSize=" + message.getEncodeSize());
         }

         if (ackBytes >= ackBatchSize) {
            if (logger.isTraceEnabled()) {
               logger.trace(this + ":: acknowledge acking " + cmi);
            }
            doAck(cmi);
         } else {
            if (logger.isTraceEnabled()) {
               logger.trace(this + ":: acknowledge setting lastAckedMessage = " + cmi);
            }
            lastAckedMessage = cmi;
         }
      }
   }

   private void doAck(final ClientMessageInternal message) throws ActiveMQException {
      ackBytes = 0;

      lastAckedMessage = null;

      if (logger.isTraceEnabled()) {
         logger.trace(this + "::Acking message " + message);
      }

      session.acknowledge(this, message);
   }

   //......
}
  • The acknowledge method of ClientConsumerImpl executes the doAck method, while the doAck method executes session.acknowledge(this, message)

ClientSessionImpl.acknowledge

activemq-artemis-2.11.0/artemis-core-client/src/main/java/org/apache/activemq/artemis/core/client/impl/ClientSessionImpl.java

public final class ClientSessionImpl implements ClientSessionInternal, FailureListener {

   //......

   public void acknowledge(final ClientConsumer consumer, final Message message) throws ActiveMQException {
      // if we're pre-acknowledging then we don't need to do anything
      if (preAcknowledge) {
         return;
      }

      checkClosed();
      if (logger.isDebugEnabled()) {
         logger.debug("client ack messageID = " + message.getMessageID());
      }

      startCall();
      try {
         sessionContext.sendACK(false, blockOnAcknowledge, consumer, message);
      } finally {
         endCall();
      }
   }

   //......
}
  • The acknowledge method of ClientSessionImpl sends ack through sessionContext.sendACK

Summary

  • The receive mode of ActiveMQMessageConsumer is pull mode, which executes jmsmsg. Setindividualknowledge() for session. Getackknowledgemode(), and coreMessage.acknowledge(), for the rest. The acknowledge method of ClientMessageImpl executes consumer.acknowledge(this)
  • The handleMessage of ClientConsumerImpl adopts push mode. It will execute buffer.addTail(message, message.getPriority()) and queueExecutor(), which will execute Runner through sessionexecution. The run method of Runner will execute callOnMessage, which will execute handler. Onmessage (message) from buffer.poll() message. If it is not null and not expired, it will execute handler. Onmessage (message). Finally, touch It is issued that the consumer.acknowledge(this) method is executed
  • The acknowledge method of ClientConsumerImpl executes the doAck method, while the doAck method executes session.acknowledge(this, message); the acknowledge method of ClientSessionImpl sends ack through sessionContext.sendACK

doc

Topics: Programming Session Java Apache REST