/******************************************************************************* * Copyright © 2012-2015 eBay Software Foundation * This program is dual licensed under the MIT and Apache 2.0 licenses. * Please see LICENSE for more information. *******************************************************************************/ package com.ebay.jetstream.messaging; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ebay.jetstream.common.ShutDownable; import com.ebay.jetstream.counter.LongCounter; import com.ebay.jetstream.counter.LongEWMACounter; import com.ebay.jetstream.management.Management; import com.ebay.jetstream.messaging.config.ContextConfig; import com.ebay.jetstream.messaging.config.MessageServiceProperties; import com.ebay.jetstream.messaging.config.TransportConfig; import com.ebay.jetstream.messaging.exception.MessageServiceException; import com.ebay.jetstream.messaging.interfaces.IMessageListener; import com.ebay.jetstream.messaging.interfaces.ITransportProvider; import com.ebay.jetstream.messaging.messagetype.JetstreamMessage; import com.ebay.jetstream.messaging.stats.MessageServiceStats; import com.ebay.jetstream.messaging.stats.MessageServiceStatsController; import com.ebay.jetstream.messaging.stats.StatsHarvestTimer; import com.ebay.jetstream.messaging.stats.TransportStats; import com.ebay.jetstream.messaging.topic.JetstreamTopic; import com.ebay.jetstream.messaging.topic.TopicInfo; import com.ebay.jetstream.util.RequestQueueProcessor; /** * @author shmurthy * */ @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD") public class MessageService implements ShutDownable { private static MessageService s_theInstance = new MessageService(); // the following data structure is used to maintain the sequence Ids // corresponding to each of the topics broadcast from a publisher process. private static final Logger LOGGER = LoggerFactory .getLogger("com.ebay.jetstream.messaging"); /** * @return */ public static MessageService getInstance() { return s_theInstance; } // the following data structure holds data about publishers whose // messages are received by the receiver. The publishers // last sent sequenceID is stored in the publisherInfo. // the key is made up of guid and ip address of sender. private final ConcurrentHashMap<String, DispatcherInfo> m_dispatcherlist; private final ConcurrentHashMap<String, TopicInfo> m_publishedTopiclist; private final ConcurrentHashMap<String, ConcurrentLinkedQueue<ListenerInfo>> m_listenerTable; private byte[] m_msgOrigAddr; private final AtomicBoolean m_initialized = new AtomicBoolean(false); RequestQueueProcessor m_msgProcessor; RequestQueueProcessor m_internalMsgProcessor; private final AtomicLong m_msgRcvCounter = new AtomicLong(0); private final Object m_lock = new Object(); private final DispatchQueueStats m_queueStats = new DispatchQueueStats(); private DispatchQueueMonitor m_dqm; private volatile MessageServiceProperties m_messageServiceProperties; // stats private final LongCounter m_totalMsgsSent = new LongCounter(); private final AtomicLong m_msgsRcvdPerSec = new AtomicLong(0); private final LongCounter m_totalMsgsRcvd = new LongCounter(); private final LongCounter m_totalMsgsDropped = new LongCounter(); private final LongCounter m_totalMsgsDroppedByNoContext = new LongCounter(); private final LongEWMACounter m_avgMsgsSentPerSec = new LongEWMACounter(60, MessageServiceTimer.sInstance().getTimer()); private final LongEWMACounter m_avgMsgsRcvdPerSec = new LongEWMACounter(60, MessageServiceTimer.sInstance().getTimer()); private StatsHarvestTimer m_statsHarvestTimer; private MessageServiceProxy m_proxy; private AtomicBoolean m_paused = new AtomicBoolean( false); private enum UpstreamQueueState { FULL, EMPTY }; private int m_twentyPercentCapacity; public boolean isPaused() { return m_paused.get(); } /** * Although this class was intended to be a singleton, the default * constructor has public access mainly because it needs to be instantiated * by Spring. */ public MessageService() { s_theInstance = this; // this is done mainly so that Spring can load // message service - generally a bad practice m_listenerTable = new ConcurrentHashMap<String, ConcurrentLinkedQueue<ListenerInfo>>(); m_dispatcherlist = new ConcurrentHashMap<String, DispatcherInfo>(); m_publishedTopiclist = new ConcurrentHashMap<String, TopicInfo>(); } /** * @param topic * @param tml */ void addSubscriber( JetstreamTopic topic, com.ebay.jetstream.messaging.interfaces.IMessageListener tml) { if (!m_listenerTable.containsKey(topic.getRootContext())) { ConcurrentLinkedQueue<ListenerInfo> llist = new ConcurrentLinkedQueue<ListenerInfo>(); ListenerInfo li = new ListenerInfo(); li.m_listener = tml; li.m_topic = topic; llist.add(li); m_listenerTable.put(topic.getRootContext(), llist); } else { ConcurrentLinkedQueue<ListenerInfo> llist = m_listenerTable .get(topic.getRootContext()); if (llist == null) return; ListenerInfo li = new ListenerInfo(); li.m_listener = tml; li.m_topic = topic; if (!llist.contains(li)) llist.add(li); else li = null; } } /** * @param context * @throws Exception */ private void createDispatcherRegisterWithAllTransports(JetstreamTopic topic) throws Exception { Enumeration<DispatcherInfo> dispatcherlist = m_dispatcherlist .elements(); while (dispatcherlist.hasMoreElements()) { DispatcherInfo dispinfo = dispatcherlist.nextElement(); if (!dispinfo.isDispatcherCreated()) { try { dispinfo.getTransport().registerListener(m_proxy); dispinfo.getTransport().registerTopic(topic); } catch (Exception e) { String message = "Failed to register transport listener - "; message += e.getMessage(); LOGGER.error( message); throw e; } dispinfo.setDispatcherCreated(true); } } } /** * @param context * @throws Exception */ void createDispatcherRegisterWithTransport(JetstreamTopic topic) throws Exception { String context = topic.getRootContext(); if (context.equals("/")) { createDispatcherRegisterWithAllTransports(topic); return; } DispatcherInfo dispinfo = m_dispatcherlist.get(context); if (dispinfo == null) { String message = "Did not find any transport from config for context - "; message += context; LOGGER.error( message); throw new MessageServiceException( MessageServiceException.TRANSPORT_ERROR, message); } if (!dispinfo.isDispatcherCreated()) { try { dispinfo.getTransport().registerListener(m_proxy); dispinfo.getTransport().registerTopic(topic); } catch (Exception e) { if (LOGGER.isWarnEnabled()) { String message = "Failed to register listner - "; message += e.getMessage(); LOGGER.warn( message); } throw e; } dispinfo.setDispatcherCreated(true); } else dispinfo.getTransport().registerTopic(topic); } /** * @param tm * @return */ boolean dispatch(JetstreamMessage tm) { // first disptach for all subscibers who have registered to listen to // all messages dispatchMessageForContext("/", tm); // next disptach to subscriber who have registered interest // in a specific context only return dispatchMessageForContext(tm.getTopic().getRootContext(), tm); } /** * @param topic * @param tm * @throws Exception */ void dispatchDownStream(JetstreamTopic topic, JetstreamMessage tm) throws Exception { if (tm != null && topic != null) { tm.setTopic(topic); tm.setMsgOrigination(m_msgOrigAddr); TopicInfo ti = getTopicInfo(topic.getTopicName()); tm.setSequenceId(ti.incSeqId()); tm.setGuid(ti.m_id); } else throw new NullPointerException("Topic or jetstreamMessage is null"); try { DispatcherInfo dispinfo = m_dispatcherlist.get(topic .getRootContext()); if (dispinfo == null) { m_totalMsgsDroppedByNoContext.increment(); // we did not find the context something is seriously wrong. // report an error and return if (LOGGER.isDebugEnabled()) { String message = "Did not find any transport from config for context - "; message += topic.getRootContext(); message += " - Dropping message!!"; LOGGER.debug( message); } return; } dispinfo.getTransport().send(tm); m_avgMsgsSentPerSec.increment(); m_totalMsgsSent.increment(); } catch (Throwable e) { String message = "Transport Exception - "; message += e.getMessage(); message += Arrays.toString(e.getStackTrace()); if (LOGGER.isDebugEnabled()) { LOGGER.debug( e.getMessage()); } throw new MessageServiceException( MessageServiceException.TRANSPORT_ERROR, message); } } /** * @param rootContext * @param tm * @return */ boolean dispatchMessageForContext(String rootContext, JetstreamMessage tm) { ConcurrentLinkedQueue<ListenerInfo> llist = null; Iterator<ListenerInfo> iter = null; if (m_listenerTable.containsKey(rootContext)) { llist = m_listenerTable.get(rootContext); if (llist == null) { return false; } try { if (llist.size() > 0) { iter = llist.iterator(); while (iter.hasNext()) { ListenerInfo li = iter.next(); if (tm.getTopic().matches(li.m_topic)) { li.m_listener.onMessage(tm); } } } } catch (IndexOutOfBoundsException ibe) { return false; } catch (Throwable e) { String message = "Exception while dispatching message for topic - " + tm.getTopic().getTopicName(); message += " - Exception - "; message += e.getMessage(); LOGGER.error( message); LOGGER.error( e.toString()); } } return false; } /** * find contexts that were previuosly provisioned but removed as part of the * config change * * @param msp * @return */ private List<String> findDeletedContexts(MessageServiceProperties msp) { LinkedList<String> deletedContexts = new LinkedList<String>(); Enumeration<String> contextlist = m_dispatcherlist.keys(); LinkedList<String> newContexts = new LinkedList<String>(); Iterator<TransportConfig> itr = msp.getTransports().iterator(); while (itr.hasNext()) { TransportConfig tc = itr.next(); Iterator<ContextConfig> contextItr = tc.getContextList().iterator(); while (contextItr.hasNext()) { ContextConfig cc = contextItr.next(); newContexts.add(cc.getContextname()); if (m_dispatcherlist.containsKey(cc.getContextname())) { DispatcherInfo dispinfo = m_dispatcherlist.get(cc .getContextname()); if (dispinfo != null) { if (!dispinfo.getTransport().getTransportConfig() .equals(tc)) // this checks for change to all // properties other than context // list deletedContexts.add(cc.getContextname()); else if (!dispinfo.getTransport().getContextConfig() .equals(cc)) // this checks if there is a // specific change to context // list deletedContexts.add(cc.getContextname()); } } } } // Now we will see if a context has been removed from the original list while (contextlist.hasMoreElements()) { String context = contextlist.nextElement(); if (!newContexts.contains(context)) deletedContexts.add(context); } return deletedContexts; } /** * @return */ DispatchQueueStats getDispatchQueueStats() { DispatchQueueStats stats = new DispatchQueueStats(); stats.setHighPriorityQueueDepth((int) m_msgProcessor .getPendingRequests()); stats.setLowPriorityQueueDepth((int) m_msgProcessor .getPendingRequests()); stats.setMaxQueueDepth(m_msgProcessor.getMaxQueueSz()); return stats; } /** * @return */ public MessageServiceProperties getMessageServiceProperties() { return m_messageServiceProperties; } /** * @return */ public MessageServiceStats getStats() { MessageServiceStats stats = new MessageServiceStats(); stats.setMsgsSentPerSec(m_avgMsgsSentPerSec.get()); stats.setTotalMsgsSent(m_totalMsgsSent.get()); stats.setMsgsRcvdPerSec(m_avgMsgsRcvdPerSec.get()); stats.setTotalMsgsRcvd(m_totalMsgsRcvd.get()); stats.setTotalMsgsLostByNoContext(m_totalMsgsDroppedByNoContext.get()); stats.setHighPriorityQueueDepth((int) m_msgProcessor .getPendingRequests()); stats.setLowPriorityQueueDepth((int) m_msgProcessor .getPendingRequests()); stats.setTotalMsgsLost(m_totalMsgsDropped.get()); stats.setPaused(isPaused()); return stats; } /** * @param topicname * @return */ private TopicInfo getTopicInfo(String topicname) { TopicInfo ti = null; if (m_publishedTopiclist.containsKey(topicname)) { ti = m_publishedTopiclist.get(topicname); } else { synchronized (m_lock) { if (!m_publishedTopiclist.containsKey(topicname)) { ti = new TopicInfo(); m_publishedTopiclist.put(topicname, ti); } else ti = m_publishedTopiclist.get(topicname); } } return ti; } /** * @return */ public List<TransportStats> getTransportStats() throws IllegalStateException { if (!isInitialized()) { throw new IllegalStateException("MessageService Not Initialized"); } LinkedList<TransportStats> tportStats = new LinkedList<TransportStats>(); Enumeration<DispatcherInfo> dispatcherlist = m_dispatcherlist .elements(); while (dispatcherlist.hasMoreElements()) { tportStats.add(dispatcherlist.nextElement().getTransport() .getStats()); } return tportStats; } /** * */ public void harvestStats() { Iterator<Entry<String, DispatcherInfo>> itr = m_dispatcherlist .entrySet().iterator(); while (itr.hasNext()) { Entry<String, DispatcherInfo> entry = itr.next(); entry.getValue().getTransport().harvestStats(); } } /** * @param msp * @throws MessageServiceException * @throws Exception */ public void init(MessageServiceProperties msp) throws MessageServiceException, Exception { setMessageServiceProperties(msp); } /** * @param configMap * @throws Exception * @throws InstantiationException * @throws IllegalAccessException * @throws ClassNotFoundException */ private void initializeMessageService() throws Exception, InstantiationException, IllegalAccessException, ClassNotFoundException { if (m_initialized.get()) return; MessageServiceProperties messageServiceProperties = getMessageServiceProperties(); try { m_msgOrigAddr = java.net.InetAddress.getLocalHost().getAddress(); } catch (Exception e) { if (LOGGER.isWarnEnabled()) { String message = "Failed to get Local Host Address - "; message += e.getMessage(); LOGGER.warn( message); } throw e; } // TODO : get capacity of queue form MessageServiceProperties. // for now hardcode m_msgProcessor = new RequestQueueProcessor( messageServiceProperties.getUpstreamDispatchQueueSize(), messageServiceProperties.getUpstreamDispatchThreadPoolSize(), "UpstreamMessageProcessor"); m_twentyPercentCapacity = (int) (m_msgProcessor.getMaxQueueSz() * 0.2); m_internalMsgProcessor = new RequestQueueProcessor(50000, 1, "InternalMsgProcessor"); m_dqm = new DispatchQueueMonitor(); m_dqm.start(); m_proxy = new MessageServiceProxy(this); if (installTransports(messageServiceProperties)) { m_initialized.set(true); // register with monitoring and control service Management.removeBeanOrFolder("MessageService/stats"); Management.addBean("MessageService/stats", new MessageServiceStatsController(this)); m_statsHarvestTimer = new StatsHarvestTimer(); MessageServiceTimer.sInstance().schedulePeriodicTask( m_statsHarvestTimer, 1000); // harvest stats every second } else { m_initialized.set(false); throw new MessageServiceException( MessageServiceException.INITIALIZATION_ERROR, "Can not install Transports"); } } /** * @param msp * @return * @throws Exception */ private boolean installTransports(MessageServiceProperties msp) throws Exception { if (msp == null) { LOGGER.error( "Service Unavailable!! MessageServiceProperties not specified - can not load transports"); return false; } Map<String, String> dnsContextMap = msp.getContextMap(); if (dnsContextMap == null) return false; Iterator<TransportConfig> itr = msp.getTransports().iterator(); while (itr.hasNext()) { TransportConfig tke = itr.next(); List<ContextConfig> contextList = tke.getContextList(); for (int i = 0; i < contextList.size(); i++) { ContextConfig cc = contextList.get(i); DispatcherInfo dispInfo = m_dispatcherlist.get(cc .getContextname()); if (dispInfo == null) dispInfo = new DispatcherInfo(); else if (dispInfo.getTransport().getContextConfig().equals(cc)) { dispInfo.getTransport().setMessageServiceProperties(msp); continue; } else { dispInfo.getTransport().shutdown(); dispInfo.setTransport(null); } ITransportProvider jetstreamTransport = instantiateTransport(tke .getTransportClass()); if (jetstreamTransport == null) continue; jetstreamTransport.setContextConfig(cc); // now get address from DNS for this context. // and then set the address and port pair. String value; if (tke.requireDNS()) { value = dnsContextMap.get(cc.getContextname()); // get IP // address // and port // pair from // DNS if (value == null) { String message = "Unable to find DNS entry for context - "; message += cc.getContextname(); LOGGER.error( message); continue; } } else { value = cc.getHostAndPort(); } StringTokenizer addressAndPort = new StringTokenizer(value, ","); // when we use DNS we will get address and port from DNS and // supply it to transport // for non-dns, the transport could get it from transport or // context config depending on transport jetstreamTransport.setAddr(addressAndPort.nextToken()); jetstreamTransport.setPort(Integer.parseInt(addressAndPort .nextToken())); jetstreamTransport.setMessageServiceProperties(msp); dispInfo.setTransport(jetstreamTransport); try { dispInfo.getTransport().init(tke, msp.getNicUsage(), msp.getDnsMap(), m_proxy); dispInfo.getTransport().registerListener(m_proxy); // this transport may be installed as part of a config // change. The service // might already have been up. Let's check in the listener // table if we have // subscriptions for the context associated with this // transport. if we do then // it is time to register that ConcurrentLinkedQueue<ListenerInfo> listeners = m_listenerTable .get(cc.getContextname()); if (listeners != null) { Iterator<ListenerInfo> listenerItr = listeners .iterator(); while (listenerItr.hasNext()) { ListenerInfo linfo = listenerItr.next(); dispInfo.getTransport() .registerTopic(linfo.m_topic); } } // we need to now handle any publishers that might have // called prepare to publish // and act as a proxy for them Enumeration<String> topicNames = m_publishedTopiclist .keys(); while (topicNames.hasMoreElements()) { String topicname = topicNames.nextElement(); JetstreamTopic topic = new JetstreamTopic(topicname); if (dispInfo.getTransport().getContextConfig() .getContextname() .equals(topic.getRootContext())) { dispInfo.getTransport().prepareToPublish( topic.getRootContext()); } } } catch (Exception e) { LOGGER.error( "Failed to install Transport for context - " + cc.getContextname() + " - " + e.getLocalizedMessage()); throw new Exception( "Failed to install Transport for context - " + cc.getContextname() + " - " + e.getLocalizedMessage(), e); } dispInfo.setDispatcherCreated(true); // BUG FIX (SRM) - Oct 25, // 2013 - was false // before - now changing // to true. m_dispatcherlist.put(cc.getContextname(), dispInfo); } } return true; } /** * @param className * @return */ ITransportProvider instantiateTransport(String className) { ITransportProvider tport = null; try { tport = (ITransportProvider) Class.forName(className).newInstance(); } catch (InstantiationException e) { LOGGER.error( "Exception while instatiating class - " + e.getMessage()); return null; } catch (IllegalAccessException e) { LOGGER.error( "Exception while instatiating class - " + e.getLocalizedMessage()); return null; } catch (ClassNotFoundException e) { String message = "Exception while instatiating class - "; message += e.getMessage(); LOGGER.error( message); return null; } return tport; } /** * @return */ public boolean isInitialized() { return m_initialized.get(); } /** * * Signal to message service to stop sending on the specified topic. This * method will unregister the TOPIC from the associated transport without * unregistering the listener. This way no more events are delivered by the * producer however whatever is in the pipeline will get delivered upstream. * * @param topic */ public void pause(JetstreamTopic topic) throws Exception { if (topic == null) throw new NullPointerException( "one or more input arguments is NULL"); DispatcherInfo dispinfo = m_dispatcherlist.get(topic.getRootContext()); if (dispinfo != null && dispinfo.isDispatcherCreated()) { dispinfo.getTransport().pause(topic); } } /* * (non-Javadoc) * * @see * com.ebay.jetstream.messaging.TransportListener#postAdvise(com.ebay.jetstream * .messaging.jetstreamMessage) */ void postAdvise(JetstreamMessage tm) { try { postMessage(tm, null); } catch (MessageServiceException e) { String message = "Exception while posting advise - "; message += e.getMessage(); LOGGER.error( message); } } /** * */ void postDispatchQueueStats() { Enumeration<DispatcherInfo> dispatcherlist = m_dispatcherlist .elements(); m_queueStats.setHighPriorityQueueDepth((int) m_msgProcessor .getPendingRequests()); m_queueStats.setLowPriorityQueueDepth((int) m_msgProcessor .getPendingRequests()); m_queueStats.setMaxQueueDepth(m_msgProcessor.getMaxQueueSz()); while (dispatcherlist.hasMoreElements()) { DispatcherInfo dispinfo = dispatcherlist.nextElement(); if (dispinfo.getTransport() != null) dispinfo.getTransport().setUpstreamDispatchQueueStats( m_queueStats); } if (m_paused.get()) { try { if (m_msgProcessor.hasAvailableCapacity(m_msgProcessor.getMaxQueueSz())) resumeTraffic(); } catch (Exception e) { LOGGER.error( "Failed to resume traffic after pausing"); } } } /** * Post a batch of messages. * * @param msgs * @param stats * @throws MessageServiceException */ void postMessage(List<JetstreamMessage> msgs, DispatchQueueStats stats) throws MessageServiceException { m_msgRcvCounter.addAndGet(msgs.size()); if ((monitorUpstreamQueueAndPauseTraffic() == UpstreamQueueState.FULL) && (m_paused.get())){ if (!m_msgProcessor.hasAvailableCapacity(m_twentyPercentCapacity)) { m_totalMsgsDropped.increment(); return; } } List<Runnable> requests = new ArrayList<Runnable>(msgs.size()); for (int i = 0, t = msgs.size(); i < t; i++) { JetstreamMessage tm = msgs.get(i); if (tm.getTopic() == null) { m_totalMsgsDropped.increment(); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Topic is not present in incoming message"); } continue; } MessageServiceRequest msr = new MessageServiceRequest(tm); msr.setPriority(tm.getPriority()); msr.setSequenceid(tm.getSequenceId()); if (msr.getPriority() == JetstreamMessage.INTERNAL_MSG_PRIORITY) { if (!m_internalMsgProcessor.processRequest(msr)) { m_totalMsgsDropped.increment(); throw new MessageServiceException( MessageServiceException.BUFFER_FULL, "Dispatch Queue Full"); } if (m_msgsRcvdPerSec.addAndGet(1) < 0) m_msgsRcvdPerSec.set(0); m_totalMsgsRcvd.increment(); } else { requests.add(msr); } } if (!requests.isEmpty()) { int batchsize = requests.size(); // SRM - July 9, 2014. we will have 1 queue for both high and lo // priority messages. This is because we don't have priority queue // with disruptor if (!m_msgProcessor.processBatch(requests)) { m_totalMsgsDropped.addAndGet(batchsize); throw new MessageServiceException( MessageServiceException.BUFFER_FULL, "High Priority Dispatch Queue Full - " + " Requested capacity = " + batchsize + " : available capacity = " + m_msgProcessor.getAvailableCapacity()); } m_avgMsgsRcvdPerSec.add(batchsize); m_totalMsgsRcvd.addAndGet(batchsize); } if (stats != null) { stats.setHighPriorityQueueDepth((int) m_msgProcessor .getPendingRequests()); stats.setLowPriorityQueueDepth((int) m_msgProcessor .getPendingRequests()); stats.setMaxQueueDepth((int) m_msgProcessor.getMaxQueueSz()); } } private void pauseTraffic() { for (Entry<String, ConcurrentLinkedQueue<ListenerInfo>> entry : m_listenerTable .entrySet()) { String context = entry.getKey(); ConcurrentLinkedQueue<ListenerInfo> listeners = entry.getValue(); if (listeners != null) { Iterator<ListenerInfo> listenerItr = listeners.iterator(); while (listenerItr.hasNext()) { ListenerInfo linfo = listenerItr.next(); try { if (!linfo.m_topic.equals(new JetstreamTopic( linfo.m_topic.getRootContext() + "/InternalStateAdvisory"))) pause(linfo.m_topic); } catch (Exception e) { LOGGER.error( "Could NOT Pause topic : " + linfo.m_topic); } } } } } private UpstreamQueueState getUpstreamQueueStatus() { if (m_msgProcessor.hasAvailableCapacity(m_msgProcessor.getMaxQueueSz() >> 1)) return UpstreamQueueState.EMPTY; else return UpstreamQueueState.FULL; } /** * - checks MessageService dispatch queue depth(highwatermark/ lowwatermark) * and raise pause/resume to producer if needed * * @throws Exception * */ private UpstreamQueueState monitorUpstreamQueueAndPauseTraffic() { if (getUpstreamQueueStatus() == UpstreamQueueState.FULL) { if (m_paused.compareAndSet(false, true)) { pauseTraffic(); } return UpstreamQueueState.FULL; } return UpstreamQueueState.EMPTY; } /** * @param tm * @param stats * @throws MessageServiceException * @throws Exception */ void postMessage(JetstreamMessage tm, DispatchQueueStats stats) throws MessageServiceException { if (tm.getTopic() == null) { m_totalMsgsDropped.increment(); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Topic is not present in incoming message"); } return; // cannot forward message without topic } m_msgRcvCounter.getAndIncrement(); if ((monitorUpstreamQueueAndPauseTraffic() == UpstreamQueueState.FULL) && (m_paused.get())){ if (!m_msgProcessor.hasAvailableCapacity(m_twentyPercentCapacity)) { m_totalMsgsDropped.increment(); return; } } MessageServiceRequest msr = new MessageServiceRequest(tm); msr.setPriority(tm.getPriority()); msr.setSequenceid(tm.getSequenceId()); if (msr.getPriority() != JetstreamMessage.INTERNAL_MSG_PRIORITY) { // SRM - July 9, 2014. we will have 1 queue for both high and low // priority messages. This is because we don't have priority queues // with disruptors if (!m_msgProcessor.processRequest(msr)) { m_totalMsgsDropped.increment(); throw new MessageServiceException( MessageServiceException.BUFFER_FULL, "High Priority Dispatch Queue Full - " + " Requested capacity = " + 1L + " : available capacity = " + m_msgProcessor.getAvailableCapacity()); } m_avgMsgsRcvdPerSec.increment(); m_totalMsgsRcvd.increment(); } else { if (!m_internalMsgProcessor.processRequest(msr)) { m_totalMsgsDropped.increment(); throw new MessageServiceException( MessageServiceException.BUFFER_FULL, "Dispatch Queue Full"); } if (m_msgsRcvdPerSec.addAndGet(1) < 0) m_msgsRcvdPerSec.set(0); m_totalMsgsRcvd.increment(); } if (stats != null) { stats.setHighPriorityQueueDepth((int) m_msgProcessor .getPendingRequests()); stats.setLowPriorityQueueDepth((int) m_msgProcessor .getPendingRequests()); stats.setMaxQueueDepth((int) m_msgProcessor.getPendingRequests()); } } /** * @param topic * @param tm * @throws MessageServiceException * @throws Exception */ public void publish(List<JetstreamTopic> topics, JetstreamMessage tm) throws MessageServiceException, Exception { if ((topics == null) || (tm == null)) throw new NullPointerException("One of the input arguments is null"); Iterator<JetstreamTopic> itr = topics.iterator(); while (itr.hasNext()) { publish(itr.next(), tm); } } /** * @param topic * @param tm * @throws MessageServiceException * @throws Exception */ public void publish(JetstreamTopic topic, JetstreamMessage tm) throws MessageServiceException, Exception { if (!m_initialized.get()) { throw new MessageServiceException( MessageServiceException.SERVICE_UNINITIALIZED, "Message service not Initialized"); } if (tm.getPriority() != JetstreamMessage.HI_PRIORITY && tm.getPriority() != JetstreamMessage.LOW_PRIORITY) throw new MessageServiceException( MessageServiceException.UNSUPPORTED_MSG_PRIORITY, "Message Priority out of range - " + Integer.toString(tm.getPriority())); dispatchDownStream(topic, tm); } /** * This method is expected to be called by a publisher to do signal to * underlying transports bound to the specified context to get ready to * receive messages to be published. The transport in turn must perform all * initialization required to publish a message. * * @param Context */ public void prepareToPublish(JetstreamTopic topic) { if (topic == null) throw new NullPointerException( "one or more input arguments is NULL"); DispatcherInfo dispinfo = m_dispatcherlist.get(topic.getRootContext()); if (dispinfo != null) { if (dispinfo.getTransport() != null) dispinfo.getTransport() .prepareToPublish(topic.getRootContext()); } } /** * */ public void resetStats() { m_avgMsgsSentPerSec.reset(); m_totalMsgsSent.reset(); m_avgMsgsRcvdPerSec.reset(); m_totalMsgsRcvd.reset(); m_totalMsgsDropped.reset(); m_totalMsgsDroppedByNoContext.reset(); } /** * * Signal to message service to resume sending on the specified topic. * Reregister the TOPIC with the associated transport with the assumption * that the listener is still registered. This way the pipe will be reopened * to deliver events upstream * * @param topic */ public void resume(JetstreamTopic topic) { DispatcherInfo dispinfo = m_dispatcherlist.get(topic.getRootContext()); if (dispinfo != null && dispinfo.isDispatcherCreated()) { dispinfo.getTransport().resume(topic); } } void resumeTraffic() throws Exception { if (m_paused.compareAndSet(true, false)) { for (Entry<String, ConcurrentLinkedQueue<ListenerInfo>> entry : m_listenerTable .entrySet()) { String context = entry.getKey(); ConcurrentLinkedQueue<ListenerInfo> listeners = entry .getValue(); if (listeners != null) { Iterator<ListenerInfo> listenerItr = listeners.iterator(); while (listenerItr.hasNext()) { ListenerInfo linfo = listenerItr.next(); try { if (!linfo.m_topic.equals(new JetstreamTopic( linfo.m_topic.getRootContext() + "/InternalStateAdvisory"))) resume(linfo.m_topic); } catch (Exception e) { LOGGER.error( "Could NOT Resume topic : " + linfo.m_topic); } } } } } } /** * @param messageServiceProperties * @throws Exception */ public void setMessageServiceProperties( MessageServiceProperties messageServiceProperties) throws Exception { if (messageServiceProperties == null) throw new NullPointerException("MessageServiceProperties is null"); m_messageServiceProperties = messageServiceProperties; if (!isInitialized()) initializeMessageService(); else { try { // first shutdown deleted contexts shutdownDeletedContexts(messageServiceProperties); // add any new contexts that have been added to configuration installTransports(messageServiceProperties); } catch (Throwable t) { LOGGER.error( "failed to apply Message Service Properties - " + t.getLocalizedMessage()); } } } /** * @throws Exception */ public void shutDown() { LOGGER.warn( "Message Service Shutting Down"); if (m_statsHarvestTimer != null) m_statsHarvestTimer.cancel(); m_avgMsgsRcvdPerSec.destroy(); m_avgMsgsSentPerSec.destroy(); if (m_msgProcessor != null) m_msgProcessor.shutdown(); if (m_internalMsgProcessor != null) m_internalMsgProcessor.shutdown(); if (m_dqm != null) m_dqm.shutdown(); Enumeration<DispatcherInfo> dispatcherlist = m_dispatcherlist .elements(); while (dispatcherlist.hasMoreElements()) { DispatcherInfo dispinfo = dispatcherlist.nextElement(); try { dispinfo.getTransport().shutdown(); } catch (Exception e) { if (LOGGER.isWarnEnabled()) { String message = "Error while shutting down - "; message += e.getMessage(); LOGGER.warn( message); } } } m_dispatcherlist.clear(); m_listenerTable.clear(); MessageServiceTimer.sInstance().shutdown(); m_initialized.set(false); } /** * This method must be called when a config changed is being applied. It * compares the context defined in the passed MessageServiceProperties with * that in the disptacher list. If a context that is present in the * dispatcher list but not present in the new config will be removed from * the dispatcher list and the associated transport instance will be * shutdown * * @param msp */ private void shutdownDeletedContexts(MessageServiceProperties msp) { List<String> deletedContextList = findDeletedContexts(msp); if (!deletedContextList.isEmpty()) { Iterator<String> deletedContextItr = deletedContextList.iterator(); while (deletedContextItr.hasNext()) { String context = deletedContextItr.next(); try { DispatcherInfo dinfo = m_dispatcherlist.remove(context); ; if (dinfo != null) dinfo.getTransport().shutdown(); } catch (Exception e) { if (LOGGER.isErrorEnabled()) { String message = "Error while applying new config and shutting down transport for context - "; message += context; message += " - "; message += e.getMessage(); LOGGER.warn( message); } } } } } /** * @param topic * @param tml * @throws MessageServiceException * @throws Exception */ public void subscribe(JetstreamTopic topic, IMessageListener tml) throws MessageServiceException, Exception { if ((topic == null) || (tml == null)) throw new NullPointerException( "one or more input arguments is NULL"); if (!m_initialized.get()) { throw new MessageServiceException( MessageServiceException.SERVICE_UNINITIALIZED, "Message service not Initialized"); } addSubscriber(topic, tml); createDispatcherRegisterWithTransport(topic); } public List<ITransportProvider> getTransportProviders() { Enumeration<DispatcherInfo> dispatcherlist = m_dispatcherlist .elements(); List<ITransportProvider> transports = new CopyOnWriteArrayList<ITransportProvider>(); while (dispatcherlist.hasMoreElements()) { DispatcherInfo dispinfo = dispatcherlist.nextElement(); transports.add(dispinfo.getTransport()); } return transports; } /** * @param topic * @param tml * @throws MessageServiceException * @throws Exception */ public void unsubscribe( JetstreamTopic topic, com.ebay.jetstream.messaging.interfaces.IMessageListener tml) throws MessageServiceException, Exception { if ((topic == null) || (tml == null)) throw new NullPointerException( "one or more input arguments is NULL"); if (!m_initialized.get()) { throw new MessageServiceException( MessageServiceException.SERVICE_UNINITIALIZED, "Message service not Initialized"); } if (m_listenerTable.containsKey(topic.getRootContext())) { ConcurrentLinkedQueue<ListenerInfo> llist = m_listenerTable .get(topic.getRootContext()); if (llist == null) return; ListenerInfo li = new ListenerInfo(); li.m_listener = tml; li.m_topic = topic; llist.remove(li); if (llist.isEmpty()) { DispatcherInfo dispinfo = m_dispatcherlist.get(topic .getRootContext()); if (dispinfo == null) { if (LOGGER.isErrorEnabled()) { String message = "Did not find any transport from config for context - "; message += topic.getRootContext(); LOGGER.error( message); return; } } else { if (dispinfo.isDispatcherCreated()) { dispinfo.getTransport().unregisterTopic(topic); } } } } } @Override public int getPendingEvents() { return 0; } }