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.exceptions.YarnRuntimeException; 036 037import com.google.common.annotations.VisibleForTesting; 038 039/** 040 * Dispatches {@link Event}s in a separate thread. Currently only single thread 041 * does that. Potentially there could be multiple channels for each event type 042 * class and a thread pool can be used to dispatch the events. 043 */ 044@SuppressWarnings("rawtypes") 045@Public 046@Evolving 047public class AsyncDispatcher extends AbstractService implements Dispatcher { 048 049 private static final Log LOG = LogFactory.getLog(AsyncDispatcher.class); 050 051 private final BlockingQueue<Event> eventQueue; 052 private volatile boolean stopped = false; 053 054 // Configuration flag for enabling/disabling draining dispatcher's events on 055 // stop functionality. 056 private volatile boolean drainEventsOnStop = false; 057 058 // Indicates all the remaining dispatcher's events on stop have been drained 059 // and processed. 060 private volatile boolean drained = true; 061 private Object waitForDrained = new Object(); 062 063 // For drainEventsOnStop enabled only, block newly coming events into the 064 // queue while stopping. 065 private volatile boolean blockNewEvents = false; 066 private final EventHandler handlerInstance = new GenericEventHandler(); 067 068 private Thread eventHandlingThread; 069 protected final Map<Class<? extends Enum>, EventHandler> eventDispatchers; 070 private boolean exitOnDispatchException; 071 072 public AsyncDispatcher() { 073 this(new LinkedBlockingQueue<Event>()); 074 } 075 076 public AsyncDispatcher(BlockingQueue<Event> eventQueue) { 077 super("Dispatcher"); 078 this.eventQueue = eventQueue; 079 this.eventDispatchers = new HashMap<Class<? extends Enum>, EventHandler>(); 080 } 081 082 Runnable createThread() { 083 return new Runnable() { 084 @Override 085 public void run() { 086 while (!stopped && !Thread.currentThread().isInterrupted()) { 087 drained = eventQueue.isEmpty(); 088 // blockNewEvents is only set when dispatcher is draining to stop, 089 // adding this check is to avoid the overhead of acquiring the lock 090 // and calling notify every time in the normal run of the loop. 091 if (blockNewEvents) { 092 synchronized (waitForDrained) { 093 if (drained) { 094 waitForDrained.notify(); 095 } 096 } 097 } 098 Event event; 099 try { 100 event = eventQueue.take(); 101 } catch(InterruptedException ie) { 102 if (!stopped) { 103 LOG.warn("AsyncDispatcher thread interrupted", ie); 104 } 105 return; 106 } 107 if (event != null) { 108 dispatch(event); 109 } 110 } 111 } 112 }; 113 } 114 115 @Override 116 protected void serviceInit(Configuration conf) throws Exception { 117 this.exitOnDispatchException = 118 conf.getBoolean(Dispatcher.DISPATCHER_EXIT_ON_ERROR_KEY, 119 Dispatcher.DEFAULT_DISPATCHER_EXIT_ON_ERROR); 120 super.serviceInit(conf); 121 } 122 123 @Override 124 protected void serviceStart() throws Exception { 125 //start all the components 126 super.serviceStart(); 127 eventHandlingThread = new Thread(createThread()); 128 eventHandlingThread.setName("AsyncDispatcher event handler"); 129 eventHandlingThread.start(); 130 } 131 132 public void setDrainEventsOnStop() { 133 drainEventsOnStop = true; 134 } 135 136 @Override 137 protected void serviceStop() throws Exception { 138 if (drainEventsOnStop) { 139 blockNewEvents = true; 140 LOG.info("AsyncDispatcher is draining to stop, igonring any new events."); 141 synchronized (waitForDrained) { 142 while (!drained && eventHandlingThread.isAlive()) { 143 waitForDrained.wait(1000); 144 LOG.info("Waiting for AsyncDispatcher to drain. Thread state is :" + 145 eventHandlingThread.getState()); 146 } 147 } 148 } 149 stopped = true; 150 if (eventHandlingThread != null) { 151 eventHandlingThread.interrupt(); 152 try { 153 eventHandlingThread.join(); 154 } catch (InterruptedException ie) { 155 LOG.warn("Interrupted Exception while stopping", ie); 156 } 157 } 158 159 // stop all the components 160 super.serviceStop(); 161 } 162 163 @SuppressWarnings("unchecked") 164 protected void dispatch(Event event) { 165 //all events go thru this loop 166 if (LOG.isDebugEnabled()) { 167 LOG.debug("Dispatching the event " + event.getClass().getName() + "." 168 + event.toString()); 169 } 170 171 Class<? extends Enum> type = event.getType().getDeclaringClass(); 172 173 try{ 174 EventHandler handler = eventDispatchers.get(type); 175 if(handler != null) { 176 handler.handle(event); 177 } else { 178 throw new Exception("No handler for registered for " + type); 179 } 180 } catch (Throwable t) { 181 //TODO Maybe log the state of the queue 182 LOG.fatal("Error in dispatcher thread", t); 183 // If serviceStop is called, we should exit this thread gracefully. 184 if (exitOnDispatchException 185 && (ShutdownHookManager.get().isShutdownInProgress()) == false 186 && stopped == false) { 187 Thread shutDownThread = new Thread(createShutDownThread()); 188 shutDownThread.setName("AsyncDispatcher ShutDown handler"); 189 shutDownThread.start(); 190 } 191 } 192 } 193 194 @SuppressWarnings("unchecked") 195 @Override 196 public void register(Class<? extends Enum> eventType, 197 EventHandler handler) { 198 /* check to see if we have a listener registered */ 199 EventHandler<Event> registeredHandler = (EventHandler<Event>) 200 eventDispatchers.get(eventType); 201 LOG.info("Registering " + eventType + " for " + handler.getClass()); 202 if (registeredHandler == null) { 203 eventDispatchers.put(eventType, handler); 204 } else if (!(registeredHandler instanceof MultiListenerHandler)){ 205 /* for multiple listeners of an event add the multiple listener handler */ 206 MultiListenerHandler multiHandler = new MultiListenerHandler(); 207 multiHandler.addHandler(registeredHandler); 208 multiHandler.addHandler(handler); 209 eventDispatchers.put(eventType, multiHandler); 210 } else { 211 /* already a multilistener, just add to it */ 212 MultiListenerHandler multiHandler 213 = (MultiListenerHandler) registeredHandler; 214 multiHandler.addHandler(handler); 215 } 216 } 217 218 @Override 219 public EventHandler getEventHandler() { 220 return handlerInstance; 221 } 222 223 class GenericEventHandler implements EventHandler<Event> { 224 public void handle(Event event) { 225 if (blockNewEvents) { 226 return; 227 } 228 drained = false; 229 230 /* all this method does is enqueue all the events onto the queue */ 231 int qSize = eventQueue.size(); 232 if (qSize !=0 && qSize %1000 == 0) { 233 LOG.info("Size of event-queue is " + qSize); 234 } 235 int remCapacity = eventQueue.remainingCapacity(); 236 if (remCapacity < 1000) { 237 LOG.warn("Very low remaining capacity in the event-queue: " 238 + remCapacity); 239 } 240 try { 241 eventQueue.put(event); 242 } catch (InterruptedException e) { 243 if (!stopped) { 244 LOG.warn("AsyncDispatcher thread interrupted", e); 245 } 246 // Need to reset drained flag to true if event queue is empty, 247 // otherwise dispatcher will hang on stop. 248 drained = eventQueue.isEmpty(); 249 throw new YarnRuntimeException(e); 250 } 251 }; 252 } 253 254 /** 255 * Multiplexing an event. Sending it to different handlers that 256 * are interested in the event. 257 * @param <T> the type of event these multiple handlers are interested in. 258 */ 259 static class MultiListenerHandler implements EventHandler<Event> { 260 List<EventHandler<Event>> listofHandlers; 261 262 public MultiListenerHandler() { 263 listofHandlers = new ArrayList<EventHandler<Event>>(); 264 } 265 266 @Override 267 public void handle(Event event) { 268 for (EventHandler<Event> handler: listofHandlers) { 269 handler.handle(event); 270 } 271 } 272 273 void addHandler(EventHandler<Event> handler) { 274 listofHandlers.add(handler); 275 } 276 277 } 278 279 Runnable createShutDownThread() { 280 return new Runnable() { 281 @Override 282 public void run() { 283 LOG.info("Exiting, bbye.."); 284 System.exit(-1); 285 } 286 }; 287 } 288 289 @VisibleForTesting 290 protected boolean isEventThreadWaiting() { 291 return eventHandlingThread.getState() == Thread.State.WAITING; 292 } 293 294 @VisibleForTesting 295 protected boolean isDrained() { 296 return this.drained; 297 } 298}