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.ipc;
20  
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.concurrent.BlockingQueue;
25  import java.util.concurrent.ThreadLocalRandom;
26  import java.util.concurrent.atomic.AtomicInteger;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.hbase.Abortable;
32  import org.apache.hadoop.hbase.HConstants;
33  import org.apache.hadoop.hbase.classification.InterfaceAudience;
34  import org.apache.hadoop.hbase.monitoring.MonitoredRPCHandler;
35  import org.apache.hadoop.util.StringUtils;
36  
37  import com.google.common.base.Preconditions;
38  import com.google.common.base.Strings;
39  
40  /**
41   * Runs the CallRunners passed here via {@link #dispatch(CallRunner)}. Subclass and add particular
42   * scheduling behavior.
43   */
44  @InterfaceAudience.Private
45  public abstract class RpcExecutor {
46    private static final Log LOG = LogFactory.getLog(RpcExecutor.class);
47  
48    protected static final int DEFAULT_CALL_QUEUE_SIZE_HARD_LIMIT = 250;
49  
50    protected volatile int currentQueueLimit;
51  
52    private final AtomicInteger activeHandlerCount = new AtomicInteger(0);
53    private final List<Handler> handlers;
54    private final int handlerCount;
55    private final String name;
56    private final AtomicInteger failedHandlerCount = new AtomicInteger(0);
57  
58    private boolean running;
59  
60    private Configuration conf = null;
61    private Abortable abortable = null;
62  
63    public RpcExecutor(final String name, final int handlerCount) {
64      this.handlers = new ArrayList<Handler>(handlerCount);
65      this.handlerCount = handlerCount;
66      this.name = Strings.nullToEmpty(name);
67    }
68  
69    public RpcExecutor(final String name, final int handlerCount, final Configuration conf,
70        final Abortable abortable) {
71      this(name, handlerCount);
72      this.conf = conf;
73      this.abortable = abortable;
74    }
75  
76    public void start(final int port) {
77      running = true;
78      startHandlers(port);
79    }
80  
81    public void stop() {
82      running = false;
83      for (Thread handler : handlers) {
84        handler.interrupt();
85      }
86    }
87  
88    public int getActiveHandlerCount() {
89      return activeHandlerCount.get();
90    }
91  
92    /** Returns the length of the pending queue */
93    public abstract int getQueueLength();
94  
95    /** Add the request to the executor queue */
96    public abstract boolean dispatch(final CallRunner callTask) throws InterruptedException;
97  
98    /** Returns the list of request queues */
99    protected abstract List<BlockingQueue<CallRunner>> getQueues();
100 
101   protected void startHandlers(final int port) {
102     List<BlockingQueue<CallRunner>> callQueues = getQueues();
103     startHandlers(null, handlerCount, callQueues, 0, callQueues.size(), port);
104   }
105 
106   /**
107    * Override if providing alternate Handler implementation.
108    */
109   protected Handler getHandler(final String name, final double handlerFailureThreshhold,
110       final BlockingQueue<CallRunner> q) {
111     return new Handler(name, handlerFailureThreshhold, q);
112   }
113 
114   /**
115    * Start up our handlers.
116    */
117   protected void startHandlers(final String nameSuffix, final int numHandlers,
118       final List<BlockingQueue<CallRunner>> callQueues,
119       final int qindex, final int qsize, final int port) {
120     final String threadPrefix = name + Strings.nullToEmpty(nameSuffix);
121     double handlerFailureThreshhold =
122         conf == null ? 1.0 : conf.getDouble(HConstants.REGION_SERVER_HANDLER_ABORT_ON_ERROR_PERCENT,
123           HConstants.DEFAULT_REGION_SERVER_HANDLER_ABORT_ON_ERROR_PERCENT);
124     for (int i = 0; i < numHandlers; i++) {
125       final int index = qindex + (i % qsize);
126       String name = "RpcServer." + threadPrefix + ".handler=" + handlers.size() + ",queue=" +
127           index + ",port=" + port;
128       Handler handler = getHandler(name, handlerFailureThreshhold, callQueues.get(index));
129       handler.start();
130       LOG.debug("Started " + name);
131       handlers.add(handler);
132     }
133   }
134 
135   /**
136    * Handler thread run the {@link CallRunner#run()} in.
137    */
138   protected class Handler extends Thread {
139     /**
140      * Q to find CallRunners to run in.
141      */
142     final BlockingQueue<CallRunner> q;
143 
144     final double handlerFailureThreshhold;
145 
146     Handler(final String name, final double handlerFailureThreshhold,
147         final BlockingQueue<CallRunner> q) {
148       super(name);
149       setDaemon(true);
150       this.q = q;
151       this.handlerFailureThreshhold = handlerFailureThreshhold;
152     }
153 
154     /**
155      * @return A {@link CallRunner}
156      * @throws InterruptedException
157      */
158     protected CallRunner getCallRunner() throws InterruptedException {
159       return this.q.take();
160     }
161 
162     @Override
163     public void run() {
164       boolean interrupted = false;
165       try {
166         while (running) {
167           try {
168             run(getCallRunner());
169           } catch (InterruptedException e) {
170             interrupted = true;
171           }
172         }
173       } catch (Exception e) {
174         LOG.warn(e);
175         throw e;
176       } finally {
177         if (interrupted) {
178           Thread.currentThread().interrupt();
179         }
180       }
181     }
182 
183     private void run(CallRunner cr) {
184       MonitoredRPCHandler status = RpcServer.getStatus();
185       cr.setStatus(status);
186       try {
187         activeHandlerCount.incrementAndGet();
188         cr.run();
189       } catch (Throwable e) {
190         if (e instanceof Error) {
191           int failedCount = failedHandlerCount.incrementAndGet();
192           if (this.handlerFailureThreshhold >= 0 &&
193               failedCount > handlerCount * this.handlerFailureThreshhold) {
194             String message = "Number of failed RpcServer handler runs exceeded threshhold " +
195               this.handlerFailureThreshhold + "; reason: " + StringUtils.stringifyException(e);
196             if (abortable != null) {
197               abortable.abort(message, e);
198             } else {
199               LOG.error("Error but can't abort because abortable is null: " +
200                   StringUtils.stringifyException(e));
201               throw e;
202             }
203           } else {
204             LOG.warn("Handler errors " + StringUtils.stringifyException(e));
205           }
206         } else {
207           LOG.warn("Handler  exception " + StringUtils.stringifyException(e));
208         }
209       } finally {
210         activeHandlerCount.decrementAndGet();
211       }
212     }
213   }
214 
215   public static abstract class QueueBalancer {
216     /**
217      * @return the index of the next queue to which a request should be inserted
218      */
219     public abstract int getNextQueue();
220   }
221 
222   public static QueueBalancer getBalancer(int queueSize) {
223     Preconditions.checkArgument(queueSize > 0, "Queue size is <= 0, must be at least 1");
224     if (queueSize == 1) {
225       return ONE_QUEUE;
226     } else {
227       return new RandomQueueBalancer(queueSize);
228     }
229   }
230 
231   /**
232    * All requests go to the first queue, at index 0
233    */
234   private static QueueBalancer ONE_QUEUE = new QueueBalancer() {
235     @Override
236     public int getNextQueue() {
237       return 0;
238     }
239   };
240 
241   /**
242    * Queue balancer that just randomly selects a queue in the range [0, num queues).
243    */
244   private static class RandomQueueBalancer extends QueueBalancer {
245     private final int queueSize;
246 
247     public RandomQueueBalancer(int queueSize) {
248       this.queueSize = queueSize;
249     }
250 
251     public int getNextQueue() {
252       return ThreadLocalRandom.current().nextInt(queueSize);
253     }
254   }
255 
256   /**
257    * Update current soft limit for executor's call queues
258    * @param conf updated configuration
259    */
260   public void resizeQueues(Configuration conf) {
261     String configKey = RpcScheduler.IPC_SERVER_MAX_CALLQUEUE_LENGTH;
262     if (name != null && name.toLowerCase().contains("priority")) {
263       configKey = RpcScheduler.IPC_SERVER_PRIORITY_MAX_CALLQUEUE_LENGTH;
264     }
265     currentQueueLimit = conf.getInt(configKey, currentQueueLimit);
266   }
267 }