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.regionserver;
20  
21  import static org.junit.Assert.assertTrue;
22  
23  import java.io.IOException;
24  import java.io.InterruptedIOException;
25  import java.util.concurrent.atomic.AtomicBoolean;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.fs.FileSystem;
31  import org.apache.hadoop.fs.Path;
32  import org.apache.hadoop.hbase.Cell;
33  import org.apache.hadoop.hbase.HBaseTestingUtility;
34  import org.apache.hadoop.hbase.HColumnDescriptor;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.HRegionInfo;
37  import org.apache.hadoop.hbase.HTableDescriptor;
38  import org.apache.hadoop.hbase.NotServingRegionException;
39  import org.apache.hadoop.hbase.TableName;
40  import org.apache.hadoop.hbase.TableNameTestRule;
41  import org.apache.hadoop.hbase.Waiter;
42  import org.apache.hadoop.hbase.client.Admin;
43  import org.apache.hadoop.hbase.client.Append;
44  import org.apache.hadoop.hbase.client.BufferedMutator;
45  import org.apache.hadoop.hbase.client.Delete;
46  import org.apache.hadoop.hbase.client.Durability;
47  import org.apache.hadoop.hbase.client.Increment;
48  import org.apache.hadoop.hbase.client.Put;
49  import org.apache.hadoop.hbase.client.Result;
50  import org.apache.hadoop.hbase.client.ResultScanner;
51  import org.apache.hadoop.hbase.client.Scan;
52  import org.apache.hadoop.hbase.client.Table;
53  import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver;
54  import org.apache.hadoop.hbase.coprocessor.ObserverContext;
55  import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
56  import org.apache.hadoop.hbase.exceptions.DeserializationException;
57  import org.apache.hadoop.hbase.filter.FilterBase;
58  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
59  import org.apache.hadoop.hbase.testclassification.LargeTests;
60  import org.apache.hadoop.hbase.testclassification.RegionServerTests;
61  import org.apache.hadoop.hbase.util.Bytes;
62  import org.apache.hadoop.hbase.wal.WAL;
63  import org.junit.After;
64  import org.junit.Before;
65  import org.junit.BeforeClass;
66  import org.junit.Rule;
67  import org.junit.Test;
68  import org.junit.experimental.categories.Category;
69  
70  @Category({RegionServerTests.class, LargeTests.class})
71  public class TestRegionInterrupt {
72  
73    private static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
74    private static final Log LOG = LogFactory.getLog(TestRegionInterrupt.class);
75  
76    static final byte[] FAMILY = Bytes.toBytes("info");
77  
78    static long sleepTime;
79  
80    @Rule
81    public TableNameTestRule name = new TableNameTestRule();
82  
83    @BeforeClass
84    public static void setUpBeforeClass() throws Exception {
85      Configuration conf = TEST_UTIL.getConfiguration();
86      conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 1);
87      conf.setClass(HConstants.REGION_IMPL, InterruptInterceptingHRegion.class, Region.class);
88      conf.setBoolean(HRegion.CLOSE_WAIT_ABORT, true);
89      // Ensure the sleep interval is long enough for interrupts to occur.
90      long waitInterval = conf.getLong(HRegion.CLOSE_WAIT_INTERVAL,
91        HRegion.DEFAULT_CLOSE_WAIT_INTERVAL);
92      sleepTime = waitInterval * 2;
93      // Try to bound the running time of this unit if expected actions do not take place.
94      conf.setLong(HRegion.CLOSE_WAIT_TIME, sleepTime * 2);
95    }
96  
97    @Before
98    public void setUp() throws Exception {
99      TEST_UTIL.startMiniCluster();
100   }
101 
102   @After
103   public void tearDown() throws Exception {
104     TEST_UTIL.shutdownMiniCluster();
105   }
106 
107   @Test
108   public void testCloseInterruptScanning() throws Exception {
109     final TableName tableName = name.getTableName();
110     LOG.info("Creating table " + tableName);
111     try (Table table = TEST_UTIL.createTable(tableName, FAMILY)) {
112       // load some data
113       TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
114       TEST_UTIL.loadTable(table, FAMILY);
115       final AtomicBoolean expectedExceptionCaught = new AtomicBoolean(false);
116       // scan the table in the background
117       Thread scanner = new Thread(new Runnable() {
118         @Override
119         public void run() {
120           Scan scan = new Scan();
121           scan.addFamily(FAMILY);
122           scan.setFilter(new DelayingFilter());
123           try {
124             LOG.info("Starting scan");
125             try (ResultScanner rs = table.getScanner(scan)) {
126               Result r;
127               do {
128                 r = rs.next();
129                 if (r != null) {
130                   LOG.info("Scanned row " + Bytes.toStringBinary(r.getRow()));
131                 }
132               } while (r != null);
133             }
134           } catch (IOException e) {
135             LOG.info("Scanner caught exception", e);
136             expectedExceptionCaught.set(true);
137           } finally {
138             LOG.info("Finished scan");
139           }
140         }
141       });
142       scanner.start();
143 
144       // Wait for the filter to begin sleeping
145       LOG.info("Waiting for scanner to start");
146       Waiter.waitFor(TEST_UTIL.getConfiguration(), 10*1000, new Waiter.Predicate<Exception>() {
147         @Override
148         public boolean evaluate() throws Exception {
149           return DelayingFilter.isSleeping();
150         }
151       });
152 
153       // Offline the table, this will trigger closing
154       LOG.info("Offlining table " + tableName);
155       TEST_UTIL.getHBaseAdmin().disableTable(tableName);
156 
157       // Wait for scanner termination
158       scanner.join();
159 
160       // When we get here the region has closed and the table is offline
161       assertTrue("Region operations were not interrupted",
162         InterruptInterceptingHRegion.wasInterrupted());
163       assertTrue("Scanner did not catch expected exception", expectedExceptionCaught.get());
164     }
165   }
166 
167   @Test
168   public void testCloseInterruptMutation() throws Exception {
169     final TableName tableName = name.getTableName();
170     final Admin admin = TEST_UTIL.getHBaseAdmin();
171     // Create the test table
172     HTableDescriptor htd = new HTableDescriptor(tableName);
173     htd.addFamily(new HColumnDescriptor(FAMILY));
174     htd.addCoprocessor(MutationDelayingCoprocessor.class.getName());
175     LOG.info("Creating table " + tableName);
176     admin.createTable(htd);
177     TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
178 
179     // Insert some data in the background
180     LOG.info("Starting writes to table " + tableName);
181     final int NUM_ROWS = 100;
182     final AtomicBoolean expectedExceptionCaught = new AtomicBoolean(false);
183     Thread inserter = new Thread(new Runnable() {
184       @Override
185       public void run() {
186         try (BufferedMutator t = admin.getConnection().getBufferedMutator(tableName)) {
187           for (int i = 0; i < NUM_ROWS; i++) {
188             LOG.info("Writing row " + i + " to " + tableName);
189             byte[] value = new byte[10], row = Bytes.toBytes(Integer.toString(i));
190             Bytes.random(value);
191             t.mutate(new Put(row).addColumn(FAMILY, HConstants.EMPTY_BYTE_ARRAY, value));
192             t.flush();
193           }
194         } catch (IOException e) {
195           LOG.info("Inserter caught exception", e);
196           expectedExceptionCaught.set(true);
197         }
198       }
199     });
200     inserter.start();
201 
202     // Wait for delayed insertion to begin
203     LOG.info("Waiting for mutations to start");
204     Waiter.waitFor(TEST_UTIL.getConfiguration(), 10*1000, new Waiter.Predicate<Exception>() {
205       @Override
206       public boolean evaluate() throws Exception {
207         return MutationDelayingCoprocessor.isSleeping();
208       }
209     });
210 
211     // Offline the table, this will trigger closing
212     LOG.info("Offlining table " + tableName);
213     admin.disableTable(tableName);
214 
215     // Wait for the inserter to finish
216     inserter.join();
217 
218     // When we get here the region has closed and the table is offline
219     assertTrue("Region operations were not interrupted",
220       InterruptInterceptingHRegion.wasInterrupted());
221     assertTrue("Inserter did not catch expected exception", expectedExceptionCaught.get());
222   }
223 
224   public static class InterruptInterceptingHRegion extends HRegion {
225 
226     private static boolean interrupted = false;
227 
228     public static boolean wasInterrupted() {
229       return interrupted;
230     }
231 
232     public InterruptInterceptingHRegion(Path tableDir, WAL wal, FileSystem fs,
233         Configuration conf, HRegionInfo regionInfo, HTableDescriptor htd,
234         RegionServerServices rsServices) {
235       super(tableDir, wal, fs, conf, regionInfo, htd, rsServices);
236     }
237 
238     public InterruptInterceptingHRegion(HRegionFileSystem fs, WAL wal, Configuration conf,
239         HTableDescriptor htd, RegionServerServices rsServices) {
240       super(fs, wal, conf, htd, rsServices);
241     }
242 
243     @Override
244     void checkInterrupt() throws NotServingRegionException, InterruptedIOException {
245       try {
246         super.checkInterrupt();
247       } catch (NotServingRegionException | InterruptedIOException e) {
248         interrupted = true;
249         throw e;
250       }
251     }
252 
253     @Override
254     IOException throwOnInterrupt(Throwable t) {
255       interrupted = true;
256       return super.throwOnInterrupt(t);
257     }
258 
259   }
260 
261   public static class DelayingFilter extends FilterBase {
262 
263     static volatile boolean sleeping = false;
264 
265     public static boolean isSleeping() {
266       return sleeping;
267     }
268 
269     @Override
270     public ReturnCode filterKeyValue(Cell v) throws IOException {
271       LOG.info("Starting sleep on " + v);
272       sleeping = true;
273       try {
274         Thread.sleep(sleepTime);
275       } catch (InterruptedException e) {
276         // restore interrupt status so region scanner can handle it as expected
277         Thread.currentThread().interrupt();
278         LOG.info("Interrupted during sleep on " + v);
279       } finally {
280         LOG.info("Done sleep on " + v);
281         sleeping = false;
282       }
283       return ReturnCode.INCLUDE;
284     }
285 
286     public static DelayingFilter parseFrom(final byte [] pbBytes)
287         throws DeserializationException {
288       // Just return a new instance.
289       return new DelayingFilter();
290     }
291 
292   }
293 
294   public static class MutationDelayingCoprocessor extends BaseRegionObserver {
295 
296     static volatile boolean sleeping = false;
297 
298     public static boolean isSleeping() {
299       return sleeping;
300     }
301 
302     private void doSleep(Region.Operation op) {
303       LOG.info("Starting sleep for " + op);
304       sleeping = true;
305       try {
306         Thread.sleep(sleepTime);
307       } catch (InterruptedException e) {
308         // restore interrupt status so doMiniBatchMutation etc. can handle it as expected
309         Thread.currentThread().interrupt();
310         LOG.info("Interrupted during " + op);
311       } finally {
312         LOG.info("Done");
313         sleeping = false;
314       }
315     }
316 
317     @Override
318     public void prePut(ObserverContext<RegionCoprocessorEnvironment> c, Put put, WALEdit edit,
319         Durability durability) throws IOException {
320       doSleep(Region.Operation.PUT);
321       super.prePut(c, put, edit, durability);
322     }
323 
324     @Override
325     public void preDelete(ObserverContext<RegionCoprocessorEnvironment> c, Delete delete,
326         WALEdit edit, Durability durability) throws IOException {
327       doSleep(Region.Operation.DELETE);
328       super.preDelete(c, delete, edit, durability);
329     }
330 
331     @Override
332     public Result preAppend(ObserverContext<RegionCoprocessorEnvironment> c, Append append)
333         throws IOException {
334       doSleep(Region.Operation.APPEND);
335       return super.preAppend(c, append);
336     }
337 
338     @Override
339     public Result preIncrement(ObserverContext<RegionCoprocessorEnvironment> c, Increment increment)
340         throws IOException {
341       doSleep(Region.Operation.INCREMENT);
342       return super.preIncrement(c, increment);
343     }
344 
345   }
346 
347 }