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