View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.thrift;
20  
21  import java.util.concurrent.ExecutorService;
22  import java.util.concurrent.LinkedBlockingQueue;
23  import java.util.concurrent.RejectedExecutionException;
24  import java.util.concurrent.SynchronousQueue;
25  import java.util.concurrent.TimeUnit;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.hbase.thrift.CallQueue.Call;
32  import org.apache.hadoop.hbase.util.Threads;
33  import org.apache.thrift.TException;
34  import org.apache.thrift.TProcessor;
35  import org.apache.thrift.protocol.TProtocol;
36  import org.apache.thrift.server.TServer;
37  import org.apache.thrift.server.TThreadPoolServer;
38  import org.apache.thrift.transport.TServerTransport;
39  import org.apache.thrift.transport.TSocket;
40  import org.apache.thrift.transport.TTransport;
41  import org.apache.thrift.transport.TTransportException;
42  
43  import com.google.common.util.concurrent.ThreadFactoryBuilder;
44  
45  /**
46   * A bounded thread pool server customized for HBase.
47   */
48  @InterfaceAudience.Private
49  public class TBoundedThreadPoolServer extends TServer {
50  
51    private static final String QUEUE_FULL_MSG =
52        "Queue is full, closing connection";
53  
54    /**
55     * The "core size" of the thread pool. New threads are created on every
56     * connection until this many threads are created.
57     */
58    public static final String MIN_WORKER_THREADS_CONF_KEY =
59        "hbase.thrift.minWorkerThreads";
60  
61    /**
62     * This default core pool size should be enough for many test scenarios. We
63     * want to override this with a much larger number (e.g. at least 200) for a
64     * large-scale production setup.
65     */
66    public static final int DEFAULT_MIN_WORKER_THREADS = 16;
67  
68    /**
69     * The maximum size of the thread pool. When the pending request queue
70     * overflows, new threads are created until their number reaches this number.
71     * After that, the server starts dropping connections.
72     */
73    public static final String MAX_WORKER_THREADS_CONF_KEY =
74        "hbase.thrift.maxWorkerThreads";
75  
76    public static final int DEFAULT_MAX_WORKER_THREADS = 1000;
77  
78    /**
79     * The maximum number of pending connections waiting in the queue. If there
80     * are no idle threads in the pool, the server queues requests. Only when
81     * the queue overflows, new threads are added, up to
82     * hbase.thrift.maxQueuedRequests threads.
83     */
84    public static final String MAX_QUEUED_REQUESTS_CONF_KEY =
85        "hbase.thrift.maxQueuedRequests";
86  
87    public static final int DEFAULT_MAX_QUEUED_REQUESTS = 1000;
88  
89    /**
90     * Default amount of time in seconds to keep a thread alive. Worker threads
91     * are stopped after being idle for this long.
92     */
93    public static final String THREAD_KEEP_ALIVE_TIME_SEC_CONF_KEY =
94        "hbase.thrift.threadKeepAliveTimeSec";
95  
96    private static final int DEFAULT_THREAD_KEEP_ALIVE_TIME_SEC = 60;
97  
98    /**
99     * Time to wait after interrupting all worker threads. This is after a clean
100    * shutdown has been attempted.
101    */
102   public static final int TIME_TO_WAIT_AFTER_SHUTDOWN_MS = 5000;
103 
104   private static final Log LOG = LogFactory.getLog(
105       TBoundedThreadPoolServer.class.getName());
106 
107   private final CallQueue callQueue;
108 
109   public static class Args extends TThreadPoolServer.Args {
110     int maxQueuedRequests;
111     int threadKeepAliveTimeSec;
112 
113     public Args(TServerTransport transport, Configuration conf) {
114       super(transport);
115       minWorkerThreads = conf.getInt(MIN_WORKER_THREADS_CONF_KEY,
116           DEFAULT_MIN_WORKER_THREADS);
117       maxWorkerThreads = conf.getInt(MAX_WORKER_THREADS_CONF_KEY,
118           DEFAULT_MAX_WORKER_THREADS);
119       maxQueuedRequests = conf.getInt(MAX_QUEUED_REQUESTS_CONF_KEY,
120           DEFAULT_MAX_QUEUED_REQUESTS);
121       threadKeepAliveTimeSec = conf.getInt(THREAD_KEEP_ALIVE_TIME_SEC_CONF_KEY,
122           DEFAULT_THREAD_KEEP_ALIVE_TIME_SEC);
123     }
124 
125     @Override
126     public String toString() {
127       return "min worker threads=" + minWorkerThreads
128           + ", max worker threads=" + maxWorkerThreads 
129           + ", max queued requests=" + maxQueuedRequests;
130     }
131   }
132 
133   /** Executor service for handling client connections */
134   private ExecutorService executorService;
135 
136   /** Flag for stopping the server */
137   private volatile boolean stopped;
138 
139   private Args serverOptions;
140 
141   public TBoundedThreadPoolServer(Args options, ThriftMetrics metrics) {
142     super(options);
143 
144     if (options.maxQueuedRequests > 0) {
145       this.callQueue = new CallQueue(
146           new LinkedBlockingQueue<Call>(options.maxQueuedRequests), metrics);
147     } else {
148       this.callQueue = new CallQueue(new SynchronousQueue<Call>(), metrics);
149     }
150 
151     ThreadFactoryBuilder tfb = new ThreadFactoryBuilder();
152     tfb.setDaemon(true);
153     tfb.setNameFormat("thrift-worker-%d");
154     executorService =
155         new THBaseThreadPoolExecutor(options.minWorkerThreads,
156             options.maxWorkerThreads, options.threadKeepAliveTimeSec,
157             TimeUnit.SECONDS, this.callQueue, tfb.build(), metrics);
158     serverOptions = options;
159   }
160 
161   @Override
162   public void serve() {
163     try {
164       serverTransport_.listen();
165     } catch (TTransportException ttx) {
166       LOG.error("Error occurred during listening.", ttx);
167       return;
168     }
169 
170     Runtime.getRuntime().addShutdownHook(
171         new Thread(getClass().getSimpleName() + "-shutdown-hook") {
172           @Override
173           public void run() {
174             TBoundedThreadPoolServer.this.stop();
175           }
176         });
177 
178     stopped = false;
179     while (!stopped && !Thread.interrupted()) {
180       TTransport client = null;
181       try {
182         client = serverTransport_.accept();
183       } catch (TTransportException ttx) {
184         if (!stopped) {
185           LOG.warn("Transport error when accepting message", ttx);
186           continue;
187         } else {
188           // The server has been stopped
189           break;
190         }
191       }
192 
193       ClientConnnection command = new ClientConnnection(client);
194       try {
195         executorService.execute(command);
196       } catch (RejectedExecutionException rex) {
197         if (client.getClass() == TSocket.class) {
198           // We expect the client to be TSocket.
199           LOG.warn(QUEUE_FULL_MSG + " from " +
200               ((TSocket) client).getSocket().getRemoteSocketAddress());
201         } else {
202           LOG.warn(QUEUE_FULL_MSG, rex);
203         }
204         client.close();
205       }
206     }
207 
208     shutdownServer();
209   }
210 
211   /**
212    * Loop until {@link ExecutorService#awaitTermination} finally does return
213    * without an interrupted exception. If we don't do this, then we'll shut
214    * down prematurely. We want to let the executor service clear its task
215    * queue, closing client sockets appropriately.
216    */
217   private void shutdownServer() {
218     executorService.shutdown();
219 
220     long msLeftToWait =
221         serverOptions.stopTimeoutUnit.toMillis(serverOptions.stopTimeoutVal);
222     long timeMillis = System.currentTimeMillis();
223 
224     LOG.info("Waiting for up to " + msLeftToWait + " ms to finish processing" +
225         " pending requests");
226     boolean interrupted = false;
227     while (msLeftToWait >= 0) {
228       try {
229         executorService.awaitTermination(msLeftToWait, TimeUnit.MILLISECONDS);
230         break;
231       } catch (InterruptedException ix) {
232         long timePassed = System.currentTimeMillis() - timeMillis;
233         msLeftToWait -= timePassed;
234         timeMillis += timePassed;
235         interrupted = true;
236       }
237     }
238 
239     LOG.info("Interrupting all worker threads and waiting for "
240         + TIME_TO_WAIT_AFTER_SHUTDOWN_MS + " ms longer");
241 
242     // This will interrupt all the threads, even those running a task.
243     executorService.shutdownNow();
244     Threads.sleepWithoutInterrupt(TIME_TO_WAIT_AFTER_SHUTDOWN_MS);
245 
246     // Preserve the interrupted status.
247     if (interrupted) {
248       Thread.currentThread().interrupt();
249     }
250     LOG.info("Thrift server shutdown complete");
251   }
252 
253   @Override
254   public void stop() {
255     stopped = true;
256     serverTransport_.interrupt();
257   }
258 
259   private class ClientConnnection implements Runnable {
260 
261     private TTransport client;
262 
263     /**
264      * Default constructor.
265      *
266      * @param client Transport to process
267      */
268     private ClientConnnection(TTransport client) {
269       this.client = client;
270     }
271 
272     /**
273      * Loops on processing a client forever
274      */
275     @Override
276     public void run() {
277       TProcessor processor = null;
278       TTransport inputTransport = null;
279       TTransport outputTransport = null;
280       TProtocol inputProtocol = null;
281       TProtocol outputProtocol = null;
282       try {
283         processor = processorFactory_.getProcessor(client);
284         inputTransport = inputTransportFactory_.getTransport(client);
285         outputTransport = outputTransportFactory_.getTransport(client);
286         inputProtocol = inputProtocolFactory_.getProtocol(inputTransport);
287         outputProtocol = outputProtocolFactory_.getProtocol(outputTransport);
288         // we check stopped_ first to make sure we're not supposed to be shutting
289         // down. this is necessary for graceful shutdown.
290         while (!stopped && processor.process(inputProtocol, outputProtocol)) {}
291       } catch (TTransportException ttx) {
292         // Assume the client died and continue silently
293       } catch (TException tx) {
294         LOG.error("Thrift error occurred during processing of message.", tx);
295       } catch (Exception x) {
296         LOG.error("Error occurred during processing of message.", x);
297       }
298 
299       if (inputTransport != null) {
300         inputTransport.close();
301       }
302 
303       if (outputTransport != null) {
304         outputTransport.close();
305       }
306     }
307   }
308 }