View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.executor;
20  
21  import com.google.common.collect.Lists;
22  import com.google.common.collect.Maps;
23  import com.google.common.util.concurrent.ThreadFactoryBuilder;
24  import java.io.IOException;
25  import java.io.Writer;
26  import java.lang.management.ThreadInfo;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Map.Entry;
30  import java.util.concurrent.BlockingQueue;
31  import java.util.concurrent.ConcurrentHashMap;
32  import java.util.concurrent.ConcurrentMap;
33  import java.util.concurrent.LinkedBlockingQueue;
34  import java.util.concurrent.ThreadPoolExecutor;
35  import java.util.concurrent.TimeUnit;
36  import java.util.concurrent.atomic.AtomicLong;
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.hadoop.hbase.classification.InterfaceAudience;
40  import org.apache.hadoop.hbase.executor.EventHandler.EventHandlerListener;
41  import org.apache.hadoop.hbase.monitoring.ThreadMonitoring;
42  
43  /**
44   * This is a generic executor service. This component abstracts a
45   * threadpool, a queue to which {@link EventType}s can be submitted,
46   * and a <code>Runnable</code> that handles the object that is added to the queue.
47   *
48   * <p>In order to create a new service, create an instance of this class and
49   * then do: <code>instance.startExecutorService("myService");</code>.  When done
50   * call {@link #shutdown()}.
51   *
52   * <p>In order to use the service created above, call
53   * {@link #submit(EventHandler)}. Register pre- and post- processing listeners
54   * by registering your implementation of {@link EventHandler.EventHandlerListener}
55   * with {@link #registerListener(EventType, EventHandler.EventHandlerListener)}.  Be sure
56   * to deregister your listener when done via {@link #unregisterListener(EventType)}.
57   */
58  @InterfaceAudience.Private
59  public class ExecutorService {
60    private static final Log LOG = LogFactory.getLog(ExecutorService.class);
61  
62    // hold the all the executors created in a map addressable by their names
63    private final ConcurrentHashMap<String, Executor> executorMap =
64      new ConcurrentHashMap<String, Executor>();
65  
66    // listeners that are called before and after an event is processed
67    private ConcurrentHashMap<EventType, EventHandlerListener> eventHandlerListeners =
68      new ConcurrentHashMap<EventType, EventHandlerListener>();
69  
70    // Name of the server hosting this executor service.
71    private final String servername;
72  
73    /**
74     * Default constructor.
75     * @param servername Name of the hosting server.
76     */
77    public ExecutorService(final String servername) {
78      super();
79      this.servername = servername;
80    }
81  
82    /**
83     * Start an executor service with a given name. If there was a service already
84     * started with the same name, this throws a RuntimeException.
85     * @param name Name of the service to start.
86     */
87    public void startExecutorService(String name, int maxThreads) {
88      if (this.executorMap.get(name) != null) {
89        throw new RuntimeException("An executor service with the name " + name +
90          " is already running!");
91      }
92      Executor hbes = new Executor(name, maxThreads, this.eventHandlerListeners);
93      if (this.executorMap.putIfAbsent(name, hbes) != null) {
94        throw new RuntimeException("An executor service with the name " + name +
95        " is already running (2)!");
96      }
97      LOG.debug("Starting executor service name=" + name +
98        ", corePoolSize=" + hbes.threadPoolExecutor.getCorePoolSize() +
99        ", maxPoolSize=" + hbes.threadPoolExecutor.getMaximumPoolSize());
100   }
101 
102   boolean isExecutorServiceRunning(String name) {
103     return this.executorMap.containsKey(name);
104   }
105 
106   public void shutdown() {
107     for(Entry<String, Executor> entry: this.executorMap.entrySet()) {
108       List<Runnable> wasRunning =
109         entry.getValue().threadPoolExecutor.shutdownNow();
110       if (!wasRunning.isEmpty()) {
111         LOG.info(entry.getValue() + " had " + wasRunning + " on shutdown");
112       }
113     }
114     this.executorMap.clear();
115   }
116 
117   Executor getExecutor(final ExecutorType type) {
118     return getExecutor(type.getExecutorName(this.servername));
119   }
120 
121   Executor getExecutor(String name) {
122     Executor executor = this.executorMap.get(name);
123     return executor;
124   }
125 
126   public ThreadPoolExecutor getExecutorThreadPool(final ExecutorType type) {
127     return getExecutor(type).getThreadPoolExecutor();
128   }
129 
130   public void startExecutorService(final ExecutorType type, final int maxThreads) {
131     String name = type.getExecutorName(this.servername);
132     if (isExecutorServiceRunning(name)) {
133       LOG.debug("Executor service " + toString() + " already running on " +
134         this.servername);
135       return;
136     }
137     startExecutorService(name, maxThreads);
138   }
139 
140   public void submit(final EventHandler eh) {
141     Executor executor = getExecutor(eh.getEventType().getExecutorServiceType());
142     if (executor == null) {
143       // This happens only when events are submitted after shutdown() was
144       // called, so dropping them should be "ok" since it means we're
145       // shutting down.
146       LOG.error("Cannot submit [" + eh + "] because the executor is missing." +
147         " Is this process shutting down?");
148     } else {
149       executor.submit(eh);
150     }
151   }
152 
153   /**
154    * Subscribe to updates before and after processing instances of
155    * {@link EventType}.  Currently only one listener per
156    * event type.
157    * @param type Type of event we're registering listener for
158    * @param listener The listener to run.
159    */
160   public void registerListener(final EventType type,
161       final EventHandlerListener listener) {
162     this.eventHandlerListeners.put(type, listener);
163   }
164 
165   /**
166    * Stop receiving updates before and after processing instances of
167    * {@link EventType}
168    * @param type Type of event we're registering listener for
169    * @return The listener we removed or null if we did not remove it.
170    */
171   public EventHandlerListener unregisterListener(final EventType type) {
172     return this.eventHandlerListeners.remove(type);
173   }
174 
175   public Map<String, ExecutorStatus> getAllExecutorStatuses() {
176     Map<String, ExecutorStatus> ret = Maps.newHashMap();
177     for (Map.Entry<String, Executor> e : executorMap.entrySet()) {
178       ret.put(e.getKey(), e.getValue().getStatus());
179     }
180     return ret;
181   }
182 
183   /**
184    * Executor instance.
185    */
186   static class Executor {
187     // how long to retain excess threads
188     static final long keepAliveTimeInMillis = 1000;
189     // the thread pool executor that services the requests
190     final TrackingThreadPoolExecutor threadPoolExecutor;
191     // work queue to use - unbounded queue
192     final BlockingQueue<Runnable> q = new LinkedBlockingQueue<Runnable>();
193     private final String name;
194     private final Map<EventType, EventHandlerListener> eventHandlerListeners;
195     private static final AtomicLong seqids = new AtomicLong(0);
196     private final long id;
197 
198     protected Executor(String name, int maxThreads,
199         final Map<EventType, EventHandlerListener> eventHandlerListeners) {
200       this.id = seqids.incrementAndGet();
201       this.name = name;
202       this.eventHandlerListeners = eventHandlerListeners;
203       // create the thread pool executor
204       this.threadPoolExecutor = new TrackingThreadPoolExecutor(
205           maxThreads, maxThreads,
206           keepAliveTimeInMillis, TimeUnit.MILLISECONDS, q);
207       // name the threads for this threadpool
208       ThreadFactoryBuilder tfb = new ThreadFactoryBuilder();
209       tfb.setNameFormat(this.name + "-%d");
210       this.threadPoolExecutor.setThreadFactory(tfb.build());
211     }
212 
213     /**
214      * Submit the event to the queue for handling.
215      * @param event
216      */
217     void submit(final EventHandler event) {
218       // If there is a listener for this type, make sure we call the before
219       // and after process methods.
220       EventHandlerListener listener =
221         this.eventHandlerListeners.get(event.getEventType());
222       if (listener != null) {
223         event.setListener(listener);
224       }
225       this.threadPoolExecutor.execute(event);
226     }
227 
228     TrackingThreadPoolExecutor getThreadPoolExecutor() {
229       return threadPoolExecutor;
230     }
231 
232     @Override
233     public String toString() {
234       return getClass().getSimpleName() + "-" + id + "-" + name;
235     }
236 
237     public ExecutorStatus getStatus() {
238       List<EventHandler> queuedEvents = Lists.newArrayList();
239       for (Runnable r : q) {
240         if (!(r instanceof EventHandler)) {
241           LOG.warn("Non-EventHandler " + r + " queued in " + name);
242           continue;
243         }
244         queuedEvents.add((EventHandler)r);
245       }
246 
247       List<RunningEventStatus> running = Lists.newArrayList();
248       for (Map.Entry<Thread, Runnable> e :
249           threadPoolExecutor.getRunningTasks().entrySet()) {
250         Runnable r = e.getValue();
251         if (!(r instanceof EventHandler)) {
252           LOG.warn("Non-EventHandler " + r + " running in " + name);
253           continue;
254         }
255         running.add(new RunningEventStatus(e.getKey(), (EventHandler)r));
256       }
257 
258       return new ExecutorStatus(this, queuedEvents, running);
259     }
260   }
261 
262   /**
263    * A subclass of ThreadPoolExecutor that keeps track of the Runnables that
264    * are executing at any given point in time.
265    */
266   static class TrackingThreadPoolExecutor extends ThreadPoolExecutor {
267     private ConcurrentMap<Thread, Runnable> running = Maps.newConcurrentMap();
268 
269     public TrackingThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
270         long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
271       super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
272     }
273 
274     @Override
275     protected void afterExecute(Runnable r, Throwable t) {
276       super.afterExecute(r, t);
277       running.remove(Thread.currentThread());
278     }
279 
280     @Override
281     protected void beforeExecute(Thread t, Runnable r) {
282       Runnable oldPut = running.put(t, r);
283       assert oldPut == null : "inconsistency for thread " + t;
284       super.beforeExecute(t, r);
285     }
286 
287     /**
288      * @return a map of the threads currently running tasks
289      * inside this executor. Each key is an active thread,
290      * and the value is the task that is currently running.
291      * Note that this is not a stable snapshot of the map.
292      */
293     public ConcurrentMap<Thread, Runnable> getRunningTasks() {
294       return running;
295     }
296   }
297 
298   /**
299    * A snapshot of the status of a particular executor. This includes
300    * the contents of the executor's pending queue, as well as the
301    * threads and events currently being processed.
302    *
303    * This is a consistent snapshot that is immutable once constructed.
304    */
305   public static class ExecutorStatus {
306     final Executor executor;
307     final List<EventHandler> queuedEvents;
308     final List<RunningEventStatus> running;
309 
310     ExecutorStatus(Executor executor,
311         List<EventHandler> queuedEvents,
312         List<RunningEventStatus> running) {
313       this.executor = executor;
314       this.queuedEvents = queuedEvents;
315       this.running = running;
316     }
317 
318     public List<EventHandler> getQueuedEvents() {
319       return queuedEvents;
320     }
321 
322     public List<RunningEventStatus> getRunning() {
323       return running;
324     }
325 
326     /**
327      * Dump a textual representation of the executor's status
328      * to the given writer.
329      *
330      * @param out the stream to write to
331      * @param indent a string prefix for each line, used for indentation
332      */
333     public void dumpTo(Writer out, String indent) throws IOException {
334       out.write(indent + "Status for executor: " + executor + "\n");
335       out.write(indent + "=======================================\n");
336       out.write(indent + queuedEvents.size() + " events queued, " +
337           running.size() + " running\n");
338       if (!queuedEvents.isEmpty()) {
339         out.write(indent + "Queued:\n");
340         for (EventHandler e : queuedEvents) {
341           out.write(indent + "  " + e + "\n");
342         }
343         out.write("\n");
344       }
345       if (!running.isEmpty()) {
346         out.write(indent + "Running:\n");
347         for (RunningEventStatus stat : running) {
348           out.write(indent + "  Running on thread '" +
349               stat.threadInfo.getThreadName() +
350               "': " + stat.event + "\n");
351           out.write(ThreadMonitoring.formatThreadInfo(
352               stat.threadInfo, indent + "  "));
353           out.write("\n");
354         }
355       }
356       out.flush();
357     }
358   }
359 
360   /**
361    * The status of a particular event that is in the middle of being
362    * handled by an executor.
363    */
364   public static class RunningEventStatus {
365     final ThreadInfo threadInfo;
366     final EventHandler event;
367 
368     public RunningEventStatus(Thread t, EventHandler event) {
369       this.threadInfo = ThreadMonitoring.getThreadInfo(t);
370       this.event = event;
371     }
372   }
373 }