/** {@inheritDoc} */ @Override public boolean sendMessageToSubscriber(ProtocolMessage protocolMessage, AndesContent content) throws AndesException { boolean sendSuccess; DeliverableAndesMetadata messageMetadata = protocolMessage.getMessage(); if (messageMetadata.isRetain()) { recordRetainedMessage(messageMetadata.getMessageID()); } // Should get the message from the list ByteBuffer message = MQTTUtils.getContentFromMetaInformation(content); // Will publish the message to the respective queue if (null != mqqtServerChannel) { try { // TODO:review - instead of getSubscribedDestination() used message destination mqqtServerChannel.distributeMessageToSubscriber( wildcardDestination, message, messageMetadata.getMessageID(), messageMetadata.getQosLevel(), messageMetadata.isPersistent(), getMqttSubscriptionID(), getSubscriberQOS(), messageMetadata); // We will indicate the ack to the kernel at this stage // For MQTT QOS 0 we do not get ack from subscriber, hence will be implicitly creating an // ack if (QOSLevel.AT_MOST_ONCE.getValue() == getSubscriberQOS() || QOSLevel.AT_MOST_ONCE.getValue() == messageMetadata.getQosLevel()) { mqqtServerChannel.implicitAck(messageMetadata.getMessageID(), getChannelID()); } sendSuccess = true; } catch (MQTTException e) { final String error = "Error occurred while delivering message to the subscriber for message :" + messageMetadata.getMessageID(); log.error(error, e); throw new AndesException(error, e); } } else { sendSuccess = false; } return sendSuccess; }
/** {@inheritDoc} */ @Override public int deliverMessageToSubscriptions(MessageDeliveryInfo messageDeliveryInfo) throws AndesException { Set<DeliverableAndesMetadata> messages = messageDeliveryInfo.getReadButUndeliveredMessages(); int sentMessageCount = 0; Iterator<DeliverableAndesMetadata> iterator = messages.iterator(); List<DeliverableAndesMetadata> droppedTopicMessagesList = new ArrayList<>(); /** * get all relevant type of subscriptions. This call does NOT return hierarchical subscriptions * for the destination. There are duplicated messages for each different subscribed destination. * For durable topic subscriptions this should return queue subscription bound to unique queue * based on subscription id */ Collection<LocalSubscription> subscriptions = subscriptionEngine.getActiveLocalSubscribers( messageDeliveryInfo.getDestination(), messageDeliveryInfo.getProtocolType(), messageDeliveryInfo.getDestinationType()); while (iterator.hasNext()) { try { DeliverableAndesMetadata message = iterator.next(); // All subscription filtering logic for topics goes here List<LocalSubscription> subscriptionsToDeliver = new ArrayList<>(); for (LocalSubscription subscription : subscriptions) { /* * If this is a topic message, remove all durable topic subscriptions here * because durable topic subscriptions will get messages via queue path. * Also need to consider the arrival time of the message. Only topic * subscribers which appeared before publishing this message should receive it */ if (subscription.isDurable() || (subscription.getSubscribeTime() > message.getArrivalTime())) { continue; } // Avoid sending if the selector of subscriber does not match if (!subscription.isMessageAcceptedBySelector(message)) { continue; } subscriptionsToDeliver.add(subscription); } if (subscriptionsToDeliver.size() == 0) { iterator.remove(); // remove buffer droppedTopicMessagesList.add(message); continue; // skip this iteration if no subscriptions for the message } /** * For normal non-durable topic we pre evaluate room for all subscribers and if all subs has * room to accept messages we send them. This means we operate to the speed of slowest * subscriber (to prevent OOM). If it is too slow to make others fast, make that topic * subscriber a durable topic subscriber. */ boolean allTopicSubscriptionsHasRoom = true; for (LocalSubscription subscription : subscriptionsToDeliver) { if (!subscription.hasRoomToAcceptMessages()) { allTopicSubscriptionsHasRoom = false; break; } } if (allTopicSubscriptionsHasRoom) { message.markAsScheduledToDeliver(subscriptionsToDeliver); // schedule message to all subscribers for (LocalSubscription localSubscription : subscriptionsToDeliver) { MessageFlusher.getInstance().deliverMessageAsynchronously(localSubscription, message); } iterator.remove(); if (log.isDebugEnabled()) { log.debug( "Removing Scheduled to send message from buffer. MsgId= " + message.getMessageID()); } sentMessageCount++; } else { if (log.isDebugEnabled()) { log.debug( "Some subscriptions for destination " + messageDeliveryInfo.getDestination() + " have max unacked messages " + message.getDestination()); } // if we continue message order will break break; } } catch (NoSuchElementException ex) { // This exception can occur because the iterator of ConcurrentSkipListSet loads the // at-the-time snapshot. // Some records could be deleted by the time the iterator reaches them. // However, this can only happen at the tail of the collection, not in middle, and it would // cause the loop // to blindly check for a batch of deleted records. // Given this situation, this loop should break so the sendFlusher can re-trigger it. // for tracing purposes can use this : log.warn("NoSuchElementException thrown",ex); log.warn("NoSuchElementException thrown. ", ex); break; } } /** * Here we do not need to have orphaned slot scenario (return slot). If there are no subscribers * slot will be consumed and metadata will be removed. We duplicate topic messages per node */ /** * delete topic messages that were dropped due to no subscriptions for the message and due to * has no room to enqueue the message. Delete call is blocking and then slot message count is * dropped in order */ MessagingEngine.getInstance().deleteMessages(droppedTopicMessagesList); return sentMessageCount; }