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}