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  package org.apache.hadoop.hbase.client;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertTrue;
22  
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.Random;
27  import java.util.concurrent.Callable;
28  import java.util.concurrent.CountDownLatch;
29  import java.util.concurrent.ExecutorService;
30  import java.util.concurrent.Executors;
31  import java.util.concurrent.Future;
32  import java.util.concurrent.atomic.AtomicInteger;
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.conf.Configuration;
38  import org.apache.hadoop.hbase.ClusterStatus;
39  import org.apache.hadoop.hbase.HBaseConfiguration;
40  import org.apache.hadoop.hbase.HBaseTestingUtility;
41  import org.apache.hadoop.hbase.HColumnDescriptor;
42  import org.apache.hadoop.hbase.HConstants;
43  import org.apache.hadoop.hbase.HTableDescriptor;
44  import org.apache.hadoop.hbase.ServerName;
45  import org.apache.hadoop.hbase.TableName;
46  import org.apache.hadoop.hbase.exceptions.PreemptiveFastFailException;
47  import org.apache.hadoop.hbase.regionserver.HRegionServer;
48  import org.apache.hadoop.hbase.regionserver.Region;
49  import org.apache.hadoop.hbase.ipc.SimpleRpcScheduler;
50  import org.apache.hadoop.hbase.testclassification.MediumTests;
51  import org.apache.hadoop.hbase.util.Bytes;
52  import org.apache.hadoop.hbase.util.test.LoadTestKVGenerator;
53  import org.junit.After;
54  import org.junit.AfterClass;
55  import org.junit.Before;
56  import org.junit.BeforeClass;
57  import org.junit.Test;
58  import org.junit.Ignore;
59  import org.junit.experimental.categories.Category;
60  
61  @Category({MediumTests.class})
62  public class TestFastFail {
63    private static final Log LOG = LogFactory.getLog(TestFastFail.class);
64    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
65    private static byte[] FAMILY = Bytes.toBytes("testFamily");
66    private static final Random random = new Random();
67    private static int SLAVES = 1;
68    private static byte[] QUALIFIER = Bytes.toBytes("testQualifier");
69    private static final int SLEEPTIME = 5000;
70  
71    /**
72     * @throws java.lang.Exception
73     */
74    @BeforeClass
75    public static void setUpBeforeClass() throws Exception {
76      // Just to prevent fastpath FIFO from picking calls up bypassing the queue.
77      TEST_UTIL.getConfiguration().set(
78        SimpleRpcScheduler.CALL_QUEUE_TYPE_CONF_KEY, "deadline");
79      TEST_UTIL.startMiniCluster(SLAVES);
80    }
81  
82    /**
83     * @throws java.lang.Exception
84     */
85    @AfterClass
86    public static void tearDownAfterClass() throws Exception {
87      TEST_UTIL.shutdownMiniCluster();
88    }
89  
90    /**
91     * @throws java.lang.Exception
92     */
93    @Before
94    public void setUp() throws Exception {
95      MyPreemptiveFastFailInterceptor.numBraveSouls.set(0);
96      CallQueueTooBigPffeInterceptor.numCallQueueTooBig.set(0);
97    }
98  
99    /**
100    * @throws java.lang.Exception
101    */
102   @After
103   public void tearDown() throws Exception {
104     // Nothing to do.
105   }
106 
107   @Ignore ("Can go zombie -- see HBASE-14421; FIX") @Test
108   public void testFastFail() throws IOException, InterruptedException {
109     Admin admin = TEST_UTIL.getHBaseAdmin();
110 
111     final String tableName = "testClientRelearningExperiment";
112     HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(Bytes
113         .toBytes(tableName)));
114     desc.addFamily(new HColumnDescriptor(FAMILY));
115     admin.createTable(desc, Bytes.toBytes("aaaa"), Bytes.toBytes("zzzz"), 32);
116     final long numRows = 1000;
117 
118     Configuration conf = TEST_UTIL.getConfiguration();
119     conf.setLong(HConstants.HBASE_CLIENT_OPERATION_TIMEOUT, SLEEPTIME * 100);
120     conf.setInt(HConstants.HBASE_CLIENT_PAUSE, SLEEPTIME / 10);
121     conf.setBoolean(HConstants.HBASE_CLIENT_FAST_FAIL_MODE_ENABLED, true);
122     conf.setLong(HConstants.HBASE_CLIENT_FAST_FAIL_THREASHOLD_MS, 0);
123     conf.setClass(HConstants.HBASE_CLIENT_FAST_FAIL_INTERCEPTOR_IMPL,
124         MyPreemptiveFastFailInterceptor.class,
125         PreemptiveFastFailInterceptor.class);
126 
127     final Connection connection = ConnectionFactory.createConnection(conf);
128 
129     /**
130      * Write numRows worth of data, so that the workers can arbitrarily read.
131      */
132     List<Put> puts = new ArrayList<>();
133     for (long i = 0; i < numRows; i++) {
134       byte[] rowKey = longToByteArrayKey(i);
135       Put put = new Put(rowKey);
136       byte[] value = rowKey; // value is the same as the row key
137       put.add(FAMILY, QUALIFIER, value);
138       puts.add(put);
139     }
140     try (Table table = connection.getTable(TableName.valueOf(tableName))) {
141       table.put(puts);
142       LOG.info("Written all puts.");
143     }
144 
145     /**
146      * The number of threads that are going to perform actions against the test
147      * table.
148      */
149     int nThreads = 100;
150     ExecutorService service = Executors.newFixedThreadPool(nThreads);
151     final CountDownLatch continueOtherHalf = new CountDownLatch(1);
152     final CountDownLatch doneHalfway = new CountDownLatch(nThreads);
153 
154     final AtomicInteger numSuccessfullThreads = new AtomicInteger(0);
155     final AtomicInteger numFailedThreads = new AtomicInteger(0);
156 
157     // The total time taken for the threads to perform the second put;
158     final AtomicLong totalTimeTaken = new AtomicLong(0);
159     final AtomicInteger numBlockedWorkers = new AtomicInteger(0);
160     final AtomicInteger numPreemptiveFastFailExceptions = new AtomicInteger(0);
161 
162     List<Future<Boolean>> futures = new ArrayList<Future<Boolean>>();
163     for (int i = 0; i < nThreads; i++) {
164       futures.add(service.submit(new Callable<Boolean>() {
165         /**
166          * The workers are going to perform a couple of reads. The second read
167          * will follow the killing of a regionserver so that we make sure that
168          * some of threads go into PreemptiveFastFailExcception
169          */
170         public Boolean call() throws Exception {
171           try (Table table = connection.getTable(TableName.valueOf(tableName))) {
172             Thread.sleep(Math.abs(random.nextInt()) % 250); // Add some jitter here
173             byte[] row = longToByteArrayKey(Math.abs(random.nextLong())
174                 % numRows);
175             Get g = new Get(row);
176             g.addColumn(FAMILY, QUALIFIER);
177             try {
178               table.get(g);
179             } catch (Exception e) {
180               LOG.debug("Get failed : ", e);
181               doneHalfway.countDown();
182               return false;
183             }
184 
185             // Done with one get, proceeding to do the next one.
186             doneHalfway.countDown();
187             continueOtherHalf.await();
188 
189             long startTime = System.currentTimeMillis();
190             g = new Get(row);
191             g.addColumn(FAMILY, QUALIFIER);
192             try {
193               table.get(g);
194               // The get was successful
195               numSuccessfullThreads.addAndGet(1);
196             } catch (Exception e) {
197               if (e instanceof PreemptiveFastFailException) {
198                 // We were issued a PreemptiveFastFailException
199                 numPreemptiveFastFailExceptions.addAndGet(1);
200               }
201               // Irrespective of PFFE, the request failed.
202               numFailedThreads.addAndGet(1);
203               return false;
204             } finally {
205               long enTime = System.currentTimeMillis();
206               totalTimeTaken.addAndGet(enTime - startTime);
207               if ((enTime - startTime) >= SLEEPTIME) {
208                 // Considering the slow workers as the blockedWorkers.
209                 // This assumes that the threads go full throttle at performing
210                 // actions. In case the thread scheduling itself is as slow as
211                 // SLEEPTIME, then this test might fail and so, we might have
212                 // set it to a higher number on slower machines.
213                 numBlockedWorkers.addAndGet(1);
214               }
215             }
216             return true;
217           } catch (Exception e) {
218             LOG.error("Caught unknown exception", e);
219             doneHalfway.countDown();
220             return false;
221           }
222         }
223       }));
224     }
225 
226     doneHalfway.await();
227 
228     ClusterStatus status = TEST_UTIL.getHBaseCluster().getClusterStatus();
229 
230     // Kill a regionserver
231     for (int i = 0; i < SLAVES; i++) {
232       HRegionServer server = TEST_UTIL.getHBaseCluster().getRegionServer(i);
233       List<Region> regions = server.getOnlineRegions(TableName.META_TABLE_NAME);
234       if (regions.size() > 0) continue; // We don't want to kill META table because that adds extra
235                                         // latencies which can't be tested very easily.
236       server.getRpcServer().stop();
237       server.stop("Testing");
238     }
239 
240     // Let the threads continue going
241     continueOtherHalf.countDown();
242 
243     Thread.sleep(2 * SLEEPTIME);
244     // Restore the cluster
245     TEST_UTIL.getHBaseCluster().restoreClusterStatus(status);
246 
247     int numThreadsReturnedFalse = 0;
248     int numThreadsReturnedTrue = 0;
249     int numThreadsThrewExceptions = 0;
250     for (Future<Boolean> f : futures) {
251       try {
252         numThreadsReturnedTrue += f.get() ? 1 : 0;
253         numThreadsReturnedFalse += f.get() ? 0 : 1;
254       } catch (Exception e) {
255         numThreadsThrewExceptions++;
256       }
257     }
258     LOG.debug("numThreadsReturnedFalse:"
259         + numThreadsReturnedFalse
260         + " numThreadsReturnedTrue:"
261         + numThreadsReturnedTrue
262         + " numThreadsThrewExceptions:"
263         + numThreadsThrewExceptions
264         + " numFailedThreads:"
265         + numFailedThreads.get()
266         + " numSuccessfullThreads:"
267         + numSuccessfullThreads.get()
268         + " numBlockedWorkers:"
269         + numBlockedWorkers.get()
270         + " totalTimeWaited: "
271         + totalTimeTaken.get()
272         / (numBlockedWorkers.get() == 0 ? Long.MAX_VALUE : numBlockedWorkers
273             .get()) + " numPFFEs: " + numPreemptiveFastFailExceptions.get());
274 
275     assertEquals("The expected number of all the successfull and the failed "
276         + "threads should equal the total number of threads that we spawned",
277         nThreads, numFailedThreads.get() + numSuccessfullThreads.get());
278     assertEquals(
279         "All the failures should be coming from the secondput failure",
280         numFailedThreads.get(), numThreadsReturnedFalse);
281     assertEquals("Number of threads that threw execution exceptions "
282         + "otherwise should be 0", numThreadsThrewExceptions, 0);
283     assertEquals("The regionservers that returned true should equal to the"
284         + " number of successful threads", numThreadsReturnedTrue,
285         numSuccessfullThreads.get());
286     /* 'should' is not worthy of an assert. Disabling because randomly this seems to randomly
287      * not but true. St.Ack 20151012
288      *
289     assertTrue(
290         "There should be atleast one thread that retried instead of failing",
291         MyPreemptiveFastFailInterceptor.numBraveSouls.get() > 0);
292     assertTrue(
293         "There should be atleast one PreemptiveFastFail exception,"
294             + " otherwise, the test makes little sense."
295             + "numPreemptiveFastFailExceptions: "
296             + numPreemptiveFastFailExceptions.get(),
297         numPreemptiveFastFailExceptions.get() > 0);
298     */
299     assertTrue(
300         "Only few thread should ideally be waiting for the dead "
301             + "regionserver to be coming back. numBlockedWorkers:"
302             + numBlockedWorkers.get() + " threads that retried : "
303             + MyPreemptiveFastFailInterceptor.numBraveSouls.get(),
304         numBlockedWorkers.get() <= MyPreemptiveFastFailInterceptor.numBraveSouls
305             .get());
306   }
307 
308   @Test
309   public void testCallQueueTooBigExceptionDoesntTriggerPffe() throws Exception {
310     Admin admin = TEST_UTIL.getHBaseAdmin();
311 
312     final String tableName = "testCallQueueTooBigException";
313     HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(Bytes
314       .toBytes(tableName)));
315     desc.addFamily(new HColumnDescriptor(FAMILY));
316     admin.createTable(desc, Bytes.toBytes("aaaa"), Bytes.toBytes("zzzz"), 3);
317 
318     Configuration conf = TEST_UTIL.getConfiguration();
319     conf.setLong(HConstants.HBASE_CLIENT_OPERATION_TIMEOUT, 100);
320     conf.setInt(HConstants.HBASE_CLIENT_PAUSE, 500);
321     conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 1);
322 
323     conf.setBoolean(HConstants.HBASE_CLIENT_FAST_FAIL_MODE_ENABLED, true);
324     conf.setLong(HConstants.HBASE_CLIENT_FAST_FAIL_THREASHOLD_MS, 0);
325     conf.setClass(HConstants.HBASE_CLIENT_FAST_FAIL_INTERCEPTOR_IMPL,
326       CallQueueTooBigPffeInterceptor.class, PreemptiveFastFailInterceptor.class);
327 
328     final Connection connection = ConnectionFactory.createConnection(conf);
329 
330     //Set max call queues size to 0
331     SimpleRpcScheduler srs = (SimpleRpcScheduler)
332       TEST_UTIL.getHBaseCluster().getRegionServer(0).getRpcServer().getScheduler();
333     Configuration newConf = HBaseConfiguration.create(TEST_UTIL.getConfiguration());
334     newConf.setInt("hbase.ipc.server.max.callqueue.length", 0);
335     srs.onConfigurationChange(newConf);
336 
337     try (Table table = connection.getTable(TableName.valueOf(tableName))) {
338       Get get = new Get(new byte[1]);
339       table.get(get);
340     } catch (Throwable ex) {
341     }
342 
343     assertEquals("We should have not entered PFFE mode on CQTBE, but we did;"
344       + " number of times this mode should have been entered:", 0,
345       CallQueueTooBigPffeInterceptor.numCallQueueTooBig.get());
346 
347     newConf = HBaseConfiguration.create(TEST_UTIL.getConfiguration());
348     newConf.setInt("hbase.ipc.server.max.callqueue.length", 250);
349     srs.onConfigurationChange(newConf);
350   }
351 
352   public static class MyPreemptiveFastFailInterceptor extends
353       PreemptiveFastFailInterceptor {
354     public static AtomicInteger numBraveSouls = new AtomicInteger();
355 
356     @Override
357     protected boolean shouldRetryInspiteOfFastFail(FailureInfo fInfo) {
358       boolean ret = super.shouldRetryInspiteOfFastFail(fInfo);
359       if (ret)
360         numBraveSouls.addAndGet(1);
361       return ret;
362     }
363 
364     public MyPreemptiveFastFailInterceptor(Configuration conf) {
365       super(conf);
366     }
367   }
368 
369   private byte[] longToByteArrayKey(long rowKey) {
370     return LoadTestKVGenerator.md5PrefixedKey(rowKey).getBytes();
371   }
372 
373   public static class CallQueueTooBigPffeInterceptor extends
374     PreemptiveFastFailInterceptor {
375     public static AtomicInteger numCallQueueTooBig = new AtomicInteger();
376 
377     @Override
378     protected void handleFailureToServer(ServerName serverName, Throwable t) {
379       super.handleFailureToServer(serverName, t);
380       numCallQueueTooBig.incrementAndGet();
381     }
382 
383     public CallQueueTooBigPffeInterceptor(Configuration conf) {
384       super(conf);
385     }
386   }
387 }