001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.yarn.event; 020 021import java.util.ArrayList; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.concurrent.BlockingQueue; 026import java.util.concurrent.LinkedBlockingQueue; 027 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.apache.hadoop.classification.InterfaceAudience.Public; 031import org.apache.hadoop.classification.InterfaceStability.Evolving; 032import org.apache.hadoop.conf.Configuration; 033import org.apache.hadoop.service.AbstractService; 034import org.apache.hadoop.util.ShutdownHookManager; 035import org.apache.hadoop.yarn.conf.YarnConfiguration; 036import org.apache.hadoop.yarn.exceptions.YarnRuntimeException; 037 038import com.google.common.annotations.VisibleForTesting; 039 040/** 041 * Dispatches {@link Event}s in a separate thread. Currently only single thread 042 * does that. Potentially there could be multiple channels for each event type 043 * class and a thread pool can be used to dispatch the events. 044 */ 045@SuppressWarnings("rawtypes") 046@Public 047@Evolving 048public class AsyncDispatcher extends AbstractService implements Dispatcher { 049 050 private static final Log LOG = LogFactory.getLog(AsyncDispatcher.class); 051 052 private final BlockingQueue<Event> eventQueue; 053 private volatile int lastEventQueueSizeLogged = 0; 054 private volatile boolean stopped = false; 055 056 // Configuration flag for enabling/disabling draining dispatcher's events on 057 // stop functionality. 058 private volatile boolean drainEventsOnStop = false; 059 060 // Indicates all the remaining dispatcher's events on stop have been drained 061 // and processed. 062 private volatile boolean drained = true; 063 private final Object waitForDrained = new Object(); 064 065 // For drainEventsOnStop enabled only, block newly coming events into the 066 // queue while stopping. 067 private volatile boolean blockNewEvents = false; 068 private final EventHandler handlerInstance = new GenericEventHandler(); 069 070 private Thread eventHandlingThread; 071 protected final Map<Class<? extends Enum>, EventHandler> eventDispatchers; 072 private boolean exitOnDispatchException; 073 074 public AsyncDispatcher() { 075 this(new LinkedBlockingQueue<Event>()); 076 } 077 078 public AsyncDispatcher(BlockingQueue<Event> eventQueue) { 079 super("Dispatcher"); 080 this.eventQueue = eventQueue; 081 this.eventDispatchers = new HashMap<Class<? extends Enum>, EventHandler>(); 082 } 083 084 Runnable createThread() { 085 return new Runnable() { 086 @Override 087 public void run() { 088 while (!stopped && !Thread.currentThread().isInterrupted()) { 089 drained = eventQueue.isEmpty(); 090 // blockNewEvents is only set when dispatcher is draining to stop, 091 // adding this check is to avoid the overhead of acquiring the lock 092 // and calling notify every time in the normal run of the loop. 093 if (blockNewEvents) { 094 synchronized (waitForDrained) { 095 if (drained) { 096 waitForDrained.notify(); 097 } 098 } 099 } 100 Event event; 101 try { 102 event = eventQueue.take(); 103 } catch(InterruptedException ie) { 104 if (!stopped) { 105 LOG.warn("AsyncDispatcher thread interrupted", ie); 106 } 107 return; 108 } 109 if (event != null) { 110 dispatch(event); 111 } 112 } 113 } 114 }; 115 } 116 117 @Override 118 protected void serviceInit(Configuration conf) throws Exception { 119 this.exitOnDispatchException = 120 conf.getBoolean(Dispatcher.DISPATCHER_EXIT_ON_ERROR_KEY, 121 Dispatcher.DEFAULT_DISPATCHER_EXIT_ON_ERROR); 122 super.serviceInit(conf); 123 } 124 125 @Override 126 protected void serviceStart() throws Exception { 127 //start all the components 128 super.serviceStart(); 129 eventHandlingThread = new Thread(createThread()); 130 eventHandlingThread.setName("AsyncDispatcher event handler"); 131 eventHandlingThread.start(); 132 } 133 134 public void setDrainEventsOnStop() { 135 drainEventsOnStop = true; 136 } 137 138 @Override 139 protected void serviceStop() throws Exception { 140 if (drainEventsOnStop) { 141 blockNewEvents = true; 142 LOG.info("AsyncDispatcher is draining to stop, igonring any new events."); 143 long endTime = System.currentTimeMillis() + getConfig() 144 .getLong(YarnConfiguration.DISPATCHER_DRAIN_EVENTS_TIMEOUT, 145 YarnConfiguration.DEFAULT_DISPATCHER_DRAIN_EVENTS_TIMEOUT); 146 147 synchronized (waitForDrained) { 148 while (!drained && eventHandlingThread != null 149 && eventHandlingThread.isAlive() 150 && System.currentTimeMillis() < endTime) { 151 waitForDrained.wait(1000); 152 LOG.info("Waiting for AsyncDispatcher to drain. Thread state is :" + 153 eventHandlingThread.getState()); 154 } 155 } 156 } 157 stopped = true; 158 if (eventHandlingThread != null) { 159 eventHandlingThread.interrupt(); 160 try { 161 eventHandlingThread.join(); 162 } catch (InterruptedException ie) { 163 LOG.warn("Interrupted Exception while stopping", ie); 164 } 165 } 166 167 // stop all the components 168 super.serviceStop(); 169 } 170 171 @SuppressWarnings("unchecked") 172 protected void dispatch(Event event) { 173 //all events go thru this loop 174 if (LOG.isDebugEnabled()) { 175 LOG.debug("Dispatching the event " + event.getClass().getName() + "." 176 + event.toString()); 177 } 178 179 Class<? extends Enum> type = event.getType().getDeclaringClass(); 180 181 try{ 182 EventHandler handler = eventDispatchers.get(type); 183 if(handler != null) { 184 handler.handle(event); 185 } else { 186 throw new Exception("No handler for registered for " + type); 187 } 188 } catch (Throwable t) { 189 //TODO Maybe log the state of the queue 190 LOG.fatal("Error in dispatcher thread", t); 191 // If serviceStop is called, we should exit this thread gracefully. 192 if (exitOnDispatchException 193 && (ShutdownHookManager.get().isShutdownInProgress()) == false 194 && stopped == false) { 195 Thread shutDownThread = new Thread(createShutDownThread()); 196 shutDownThread.setName("AsyncDispatcher ShutDown handler"); 197 shutDownThread.start(); 198 } 199 } 200 } 201 202 @SuppressWarnings("unchecked") 203 @Override 204 public void register(Class<? extends Enum> eventType, 205 EventHandler handler) { 206 /* check to see if we have a listener registered */ 207 EventHandler<Event> registeredHandler = (EventHandler<Event>) 208 eventDispatchers.get(eventType); 209 LOG.info("Registering " + eventType + " for " + handler.getClass()); 210 if (registeredHandler == null) { 211 eventDispatchers.put(eventType, handler); 212 } else if (!(registeredHandler instanceof MultiListenerHandler)){ 213 /* for multiple listeners of an event add the multiple listener handler */ 214 MultiListenerHandler multiHandler = new MultiListenerHandler(); 215 multiHandler.addHandler(registeredHandler); 216 multiHandler.addHandler(handler); 217 eventDispatchers.put(eventType, multiHandler); 218 } else { 219 /* already a multilistener, just add to it */ 220 MultiListenerHandler multiHandler 221 = (MultiListenerHandler) registeredHandler; 222 multiHandler.addHandler(handler); 223 } 224 } 225 226 @Override 227 public EventHandler getEventHandler() { 228 return handlerInstance; 229 } 230 231 class GenericEventHandler implements EventHandler<Event> { 232 public void handle(Event event) { 233 if (blockNewEvents) { 234 return; 235 } 236 drained = false; 237 238 /* all this method does is enqueue all the events onto the queue */ 239 int qSize = eventQueue.size(); 240 if (qSize != 0 && qSize % 1000 == 0 241 && lastEventQueueSizeLogged != qSize) { 242 lastEventQueueSizeLogged = qSize; 243 LOG.info("Size of event-queue is " + qSize); 244 } 245 int remCapacity = eventQueue.remainingCapacity(); 246 if (remCapacity < 1000) { 247 LOG.warn("Very low remaining capacity in the event-queue: " 248 + remCapacity); 249 } 250 try { 251 eventQueue.put(event); 252 } catch (InterruptedException e) { 253 if (!stopped) { 254 LOG.warn("AsyncDispatcher thread interrupted", e); 255 } 256 // Need to reset drained flag to true if event queue is empty, 257 // otherwise dispatcher will hang on stop. 258 drained = eventQueue.isEmpty(); 259 throw new YarnRuntimeException(e); 260 } 261 }; 262 } 263 264 /** 265 * Multiplexing an event. Sending it to different handlers that 266 * are interested in the event. 267 * @param <T> the type of event these multiple handlers are interested in. 268 */ 269 static class MultiListenerHandler implements EventHandler<Event> { 270 List<EventHandler<Event>> listofHandlers; 271 272 public MultiListenerHandler() { 273 listofHandlers = new ArrayList<EventHandler<Event>>(); 274 } 275 276 @Override 277 public void handle(Event event) { 278 for (EventHandler<Event> handler: listofHandlers) { 279 handler.handle(event); 280 } 281 } 282 283 void addHandler(EventHandler<Event> handler) { 284 listofHandlers.add(handler); 285 } 286 287 } 288 289 Runnable createShutDownThread() { 290 return new Runnable() { 291 @Override 292 public void run() { 293 LOG.info("Exiting, bbye.."); 294 System.exit(-1); 295 } 296 }; 297 } 298 299 @VisibleForTesting 300 protected boolean isEventThreadWaiting() { 301 return eventHandlingThread.getState() == Thread.State.WAITING; 302 } 303 304 @VisibleForTesting 305 protected boolean isDrained() { 306 return this.drained; 307 } 308}