/** * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.openflowplugin.openflow.md.queue; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.opendaylight.openflowplugin.api.openflow.md.core.IMDMessageTranslator; import org.opendaylight.openflowplugin.api.openflow.md.core.TranslatorKey; import org.opendaylight.openflowplugin.api.openflow.md.queue.HarvesterHandle; import org.opendaylight.openflowplugin.api.openflow.md.queue.PopListener; import org.opendaylight.openflowplugin.api.openflow.md.queue.QueueItem; import org.opendaylight.openflowplugin.api.openflow.md.queue.QueueKeeper; import org.opendaylight.openflowplugin.api.openflow.md.queue.QueueProcessor; import org.opendaylight.openflowplugin.api.openflow.statistics.MessageSpy; import org.opendaylight.openflowplugin.api.openflow.statistics.MessageSpy.STATISTIC_GROUP; import org.opendaylight.openflowplugin.openflow.md.core.ThreadPoolLoggingExecutor; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.OfHeader; import org.opendaylight.yangtools.yang.binding.DataContainer; import org.opendaylight.yangtools.yang.binding.DataObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * {@link org.opendaylight.openflowplugin.api.openflow.md.queue.QueueKeeper} implementation focused to keep order and use up mutiple threads for translation phase. * <br> * There is internal thread pool of limited size ({@link QueueProcessorLightImpl#setProcessingPoolSize(int)}) * dedicated to translation. Then there is singleThreadPool dedicated to publishing (via popListeners) * <br> * Workflow: * <ol> * <li>upon message push ticket is created and enqueued</li> * <li>available threads from internal pool translate the massage wrapped in ticket</li> * <li>when translation of particular message is finished, result is set in future result of wrapping ticket<br> * (order of tickets in queue is not touched during translate) * </li> * <li>at the end of queue there is {@link TicketFinisher} running in singleThreadPool and for each ticket it does: * <ol> * <li>invoke blocking {@link BlockingQueue#take()} method in order to get the oldest ticket</li> * <li>invoke blocking {@link Future#get()} on the dequeued ticket</li> * <li>as soon as the result of translation is available, appropriate popListener is invoked</li> * </ol> * and this way the order of messages is preserved and also multiple threads are used by translating * </li> * </ol> * * */ public class QueueProcessorLightImpl implements QueueProcessor<OfHeader, DataObject> { private static final Logger LOG = LoggerFactory .getLogger(QueueProcessorLightImpl.class); private BlockingQueue<TicketResult<DataObject>> ticketQueue; private ThreadPoolExecutor processorPool; private int processingPoolSize = 4; private ExecutorService harvesterPool; private ExecutorService finisherPool; protected Map<Class<? extends DataObject>, Collection<PopListener<DataObject>>> popListenersMapping; private Map<TranslatorKey, Collection<IMDMessageTranslator<OfHeader, List<DataObject>>>> translatorMapping; private TicketProcessorFactory<OfHeader, DataObject> ticketProcessorFactory; private MessageSpy<DataContainer> messageSpy; protected Collection<QueueKeeper<OfHeader>> messageSources; private QueueKeeperHarvester<OfHeader> harvester; protected TicketFinisher<DataObject> finisher; /** * prepare queue */ public void init() { int ticketQueueCapacity = 1500; ticketQueue = new ArrayBlockingQueue<>(ticketQueueCapacity); /* * TODO FIXME - DOES THIS REALLY NEED TO BE CONCURRENT? Can we figure out * a better lifecycle? Why does this have to be a Set? */ messageSources = new CopyOnWriteArraySet<>(); processorPool = new ThreadPoolLoggingExecutor(processingPoolSize, processingPoolSize, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(ticketQueueCapacity), "OFmsgProcessor"); // force blocking when pool queue is full processorPool.setRejectedExecutionHandler(new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { try { executor.getQueue().put(r); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException(e); } } }); harvesterPool = new ThreadPoolLoggingExecutor(1, 1, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1), "OFmsgHarvester"); finisherPool = new ThreadPoolLoggingExecutor(1, 1, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1), "OFmsgFinisher"); finisher = new TicketFinisherImpl( ticketQueue, popListenersMapping); finisherPool.execute(finisher); harvester = new QueueKeeperHarvester<OfHeader>(this, messageSources); harvesterPool.execute(harvester); ticketProcessorFactory = new TicketProcessorFactoryImpl(); ticketProcessorFactory.setTranslatorMapping(translatorMapping); ticketProcessorFactory.setSpy(messageSpy); ticketProcessorFactory.setTicketFinisher(finisher); } /** * stop processing queue */ public void shutdown() { processorPool.shutdown(); } @Override public void enqueueQueueItem(QueueItem<OfHeader> queueItem) { messageSpy.spyMessage(queueItem.getMessage(), STATISTIC_GROUP.FROM_SWITCH_ENQUEUED); TicketImpl<OfHeader, DataObject> ticket = new TicketImpl<>(); ticket.setConductor(queueItem.getConnectionConductor()); ticket.setMessage(queueItem.getMessage()); ticket.setQueueType(queueItem.getQueueType()); LOG.trace("ticket scheduling: {}, ticket: {}", queueItem.getMessage().getImplementedInterface().getSimpleName(), System.identityHashCode(queueItem)); scheduleTicket(ticket); } @Override public void directProcessQueueItem(QueueItem<OfHeader> queueItem) { messageSpy.spyMessage(queueItem.getMessage(), STATISTIC_GROUP.FROM_SWITCH_ENQUEUED); TicketImpl<OfHeader, DataObject> ticket = new TicketImpl<>(); ticket.setConductor(queueItem.getConnectionConductor()); ticket.setMessage(queueItem.getMessage()); LOG.debug("ticket scheduling: {}, ticket: {}", queueItem.getMessage().getImplementedInterface().getSimpleName(), System.identityHashCode(queueItem)); ticketProcessorFactory.createProcessor(ticket).run(); // publish notification finisher.firePopNotification(ticket.getDirectResult()); } /** * @param ticket */ private void scheduleTicket(Ticket<OfHeader, DataObject> ticket) { switch (ticket.getQueueType()) { case DEFAULT: Runnable ticketProcessor = ticketProcessorFactory.createProcessor(ticket); processorPool.execute(ticketProcessor); try { ticketQueue.put(ticket); } catch (InterruptedException e) { LOG.warn("enqeueue of unordered message ticket failed", e); } break; case UNORDERED: Runnable ticketProcessorSync = ticketProcessorFactory.createSyncProcessor(ticket); processorPool.execute(ticketProcessorSync); break; default: LOG.warn("unsupported enqueue type: {}", ticket.getQueueType()); } } /** * @param poolSize the poolSize to set */ public void setProcessingPoolSize(int poolSize) { this.processingPoolSize = poolSize; } @Override public void setTranslatorMapping( Map<TranslatorKey, Collection<IMDMessageTranslator<OfHeader, List<DataObject>>>> translatorMapping) { this.translatorMapping = translatorMapping; } @Override public void setPopListenersMapping( Map<Class<? extends DataObject>, Collection<PopListener<DataObject>>> popListenersMapping) { this.popListenersMapping = popListenersMapping; } /** * @param messageSpy the messageSpy to set */ public void setMessageSpy(MessageSpy<DataContainer> messageSpy) { this.messageSpy = messageSpy; } @Override public AutoCloseable registerMessageSource(QueueKeeper<OfHeader> queue) { boolean added = messageSources.add(queue); if (! added) { LOG.debug("registration of message source queue failed - already registered"); } MessageSourcePollRegistration<QueueKeeper<OfHeader>> queuePollRegistration = new MessageSourcePollRegistration<>(this, queue); return queuePollRegistration; } @Override public boolean unregisterMessageSource(QueueKeeper<OfHeader> queue) { return messageSources.remove(queue); } @Override public Collection<QueueKeeper<OfHeader>> getMessageSources() { return messageSources; } @Override public HarvesterHandle getHarvesterHandle() { return harvester; } }