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.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.mockito.Mockito.mock;
24  import static org.mockito.Mockito.when;
25  
26  import org.apache.hadoop.conf.Configuration;
27  import org.apache.hadoop.fs.FileSystem;
28  import org.apache.hadoop.fs.Path;
29  import org.apache.hadoop.hbase.HBaseTestingUtility;
30  import org.apache.hadoop.hbase.HColumnDescriptor;
31  import org.apache.hadoop.hbase.HRegionInfo;
32  import org.apache.hadoop.hbase.HTableDescriptor;
33  import org.apache.hadoop.hbase.Stoppable;
34  import org.apache.hadoop.hbase.TableName;
35  import org.apache.hadoop.hbase.client.Put;
36  import org.apache.hadoop.hbase.testclassification.MediumTests;
37  import org.apache.hadoop.hbase.testclassification.RegionServerTests;
38  import org.apache.hadoop.hbase.util.Bytes;
39  import org.apache.hadoop.hbase.util.FSUtils;
40  import org.apache.hadoop.hbase.wal.WALFactory;
41  import org.junit.After;
42  import org.junit.Before;
43  import org.junit.Test;
44  import org.junit.experimental.categories.Category;
45  
46  import java.io.IOException;
47  import java.io.InterruptedIOException;
48  import java.util.ArrayList;
49  import java.util.Collection;
50  import java.util.List;
51  import java.util.concurrent.atomic.AtomicBoolean;
52  import java.util.concurrent.atomic.AtomicReference;
53  
54  /**
55   * Tests a race condition between archiving of compacted files in CompactedHFilesDischarger chore
56   * and HRegion.close();
57   */
58  @Category({RegionServerTests.class, MediumTests.class})
59  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER",
60    justification="Use of an atomic type both as monitor and condition variable is intended")
61  public class TestCompactionArchiveConcurrentClose {
62    public HBaseTestingUtility testUtil;
63  
64    private Path testDir;
65    private AtomicBoolean archived = new AtomicBoolean();
66  
67    @Before
68    public void setup() throws Exception {
69      testUtil = HBaseTestingUtility.createLocalHTU();
70      testDir = testUtil.getDataTestDir("TestStoreFileRefresherChore");
71      FSUtils.setRootDir(testUtil.getConfiguration(), testDir);
72    }
73  
74    @After
75    public void tearDown() throws Exception {
76      testUtil.cleanupTestDir();
77    }
78  
79    @Test
80    public void testStoreCloseAndDischargeRunningInParallel() throws Exception {
81      byte[] fam = Bytes.toBytes("f");
82      byte[] col = Bytes.toBytes("c");
83      byte[] val = Bytes.toBytes("val");
84  
85      TableName tableName = TableName.valueOf(getClass().getSimpleName());
86      HTableDescriptor htd = new HTableDescriptor(tableName);
87      htd.addFamily(new HColumnDescriptor(fam));
88      HRegionInfo info = new HRegionInfo(tableName, null, null, false);
89      final Region region = initHRegion(htd, info);
90      RegionServerServices rss = mock(RegionServerServices.class);
91      List<Region> regions = new ArrayList<Region>();
92      regions.add(region);
93      when(rss.getOnlineRegions()).thenReturn(regions);
94  
95      // Create the cleaner object
96      final CompactedHFilesDischarger cleaner =
97          new CompactedHFilesDischarger(1000, (Stoppable) null, rss, false);
98      // Add some data to the region and do some flushes
99      int batchSize = 10;
100     int fileCount = 10;
101     for (int f = 0; f < fileCount; f++) {
102       int start = f * batchSize;
103       for (int i = start; i < start + batchSize; i++) {
104         Put p = new Put(Bytes.toBytes("row" + i));
105         p.addColumn(fam, col, val);
106         region.put(p);
107       }
108       // flush them
109       region.flush(true);
110     }
111 
112     Store store = region.getStore(fam);
113     assertEquals(fileCount, store.getStorefilesCount());
114 
115     Collection<StoreFile> storefiles = store.getStorefiles();
116     // None of the files should be in compacted state.
117     for (StoreFile file : storefiles) {
118       assertFalse(file.isCompactedAway());
119     }
120     // Do compaction
121     region.compact(true);
122 
123     // now run the cleaner with a concurrent close
124     Thread cleanerThread = new Thread() {
125       public void run() {
126         cleaner.chore();
127       }
128     };
129     cleanerThread.start();
130     // wait for cleaner to pause
131     synchronized (archived) {
132       if (!archived.get()) {
133         archived.wait();
134       }
135     }
136     final AtomicReference<Exception> closeException = new AtomicReference<>();
137     Thread closeThread = new Thread() {
138       public void run() {
139         // wait for the chore to complete and call close
140         try {
141           ((HRegion) region).close();
142         } catch (IOException e) {
143           closeException.set(e);
144         }
145       }
146     };
147     closeThread.start();
148     // no error should occur after the execution of the test
149     closeThread.join();
150     cleanerThread.join();
151 
152     if (closeException.get() != null) {
153       throw closeException.get();
154     }
155   }
156 
157   private Region initHRegion(HTableDescriptor htd, HRegionInfo info)
158       throws IOException {
159     Configuration conf = testUtil.getConfiguration();
160     Path tableDir = FSUtils.getTableDir(testDir, htd.getTableName());
161 
162     HRegionFileSystem fs = new WaitingHRegionFileSystem(conf, tableDir.getFileSystem(conf),
163         tableDir, info);
164     final Configuration walConf = new Configuration(conf);
165     FSUtils.setRootDir(walConf, tableDir);
166     final WALFactory wals = new WALFactory(walConf, null, "log_" + info.getEncodedName());
167     HRegion region =
168         new HRegion(fs, wals.getWAL(info.getEncodedNameAsBytes(), info.getTable().getNamespace()),
169             conf, htd, null);
170 
171     region.initialize();
172 
173     return region;
174   }
175 
176   private class WaitingHRegionFileSystem extends HRegionFileSystem {
177 
178     public WaitingHRegionFileSystem(final Configuration conf, final FileSystem fs,
179         final Path tableDir, final HRegionInfo regionInfo) {
180       super(conf, fs, tableDir, regionInfo);
181     }
182 
183     @Override
184     public void removeStoreFiles(String familyName, Collection<StoreFile> storeFiles)
185         throws IOException {
186       super.removeStoreFiles(familyName, storeFiles);
187       archived.set(true);
188       synchronized (archived) {
189         archived.notifyAll();
190       }
191       try {
192         // unfortunately we can't use a stronger barrier here as the fix synchronizing
193         // the race condition will then block
194         Thread.sleep(100);
195       } catch (InterruptedException ie) {
196         throw new InterruptedIOException("Interrupted waiting for latch");
197       }
198     }
199   }
200 }