001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.regionserver; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.mockito.Mockito.mock; 023 024import java.io.IOException; 025import java.io.InterruptedIOException; 026import java.util.ArrayList; 027import java.util.Collection; 028import java.util.List; 029import java.util.concurrent.atomic.AtomicBoolean; 030import java.util.concurrent.atomic.AtomicReference; 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.fs.FileSystem; 033import org.apache.hadoop.fs.Path; 034import org.apache.hadoop.hbase.HBaseClassTestRule; 035import org.apache.hadoop.hbase.HBaseTestingUtility; 036import org.apache.hadoop.hbase.Stoppable; 037import org.apache.hadoop.hbase.TableName; 038import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 039import org.apache.hadoop.hbase.client.Put; 040import org.apache.hadoop.hbase.client.RegionInfo; 041import org.apache.hadoop.hbase.client.RegionInfoBuilder; 042import org.apache.hadoop.hbase.client.TableDescriptor; 043import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 044import org.apache.hadoop.hbase.testclassification.MediumTests; 045import org.apache.hadoop.hbase.testclassification.RegionServerTests; 046import org.apache.hadoop.hbase.util.Bytes; 047import org.apache.hadoop.hbase.util.FSUtils; 048import org.apache.hadoop.hbase.wal.WALFactory; 049import org.junit.After; 050import org.junit.Before; 051import org.junit.ClassRule; 052import org.junit.Rule; 053import org.junit.Test; 054import org.junit.experimental.categories.Category; 055import org.junit.rules.TestName; 056import org.mockito.Mockito; 057 058/** 059 * Tests a race condition between archiving of compacted files in CompactedHFilesDischarger chore 060 * and HRegion.close(); 061 */ 062@Category({RegionServerTests.class, MediumTests.class}) 063public class TestCompactionArchiveConcurrentClose { 064 065 @ClassRule 066 public static final HBaseClassTestRule CLASS_RULE = 067 HBaseClassTestRule.forClass(TestCompactionArchiveConcurrentClose.class); 068 069 public HBaseTestingUtility testUtil; 070 071 private Path testDir; 072 private AtomicBoolean archived = new AtomicBoolean(); 073 074 @Rule 075 public TestName name = new TestName(); 076 077 @Before 078 public void setup() throws Exception { 079 testUtil = HBaseTestingUtility.createLocalHTU(); 080 testDir = testUtil.getDataTestDir("TestStoreFileRefresherChore"); 081 FSUtils.setRootDir(testUtil.getConfiguration(), testDir); 082 } 083 084 @After 085 public void tearDown() throws Exception { 086 testUtil.cleanupTestDir(); 087 } 088 089 @Test 090 public void testStoreCloseAndDischargeRunningInParallel() throws Exception { 091 byte[] fam = Bytes.toBytes("f"); 092 byte[] col = Bytes.toBytes("c"); 093 byte[] val = Bytes.toBytes("val"); 094 095 TableName tableName = TableName.valueOf(name.getMethodName()); 096 TableDescriptor htd = TableDescriptorBuilder.newBuilder(tableName) 097 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(fam)).build(); 098 RegionInfo info = RegionInfoBuilder.newBuilder(tableName).build(); 099 HRegion region = initHRegion(htd, info); 100 RegionServerServices rss = mock(RegionServerServices.class); 101 List<HRegion> regions = new ArrayList<>(); 102 regions.add(region); 103 Mockito.doReturn(regions).when(rss).getRegions(); 104 105 // Create the cleaner object 106 CompactedHFilesDischarger cleaner = 107 new CompactedHFilesDischarger(1000, (Stoppable) null, rss, false); 108 // Add some data to the region and do some flushes 109 int batchSize = 10; 110 int fileCount = 10; 111 for (int f = 0; f < fileCount; f++) { 112 int start = f * batchSize; 113 for (int i = start; i < start + batchSize; i++) { 114 Put p = new Put(Bytes.toBytes("row" + i)); 115 p.addColumn(fam, col, val); 116 region.put(p); 117 } 118 // flush them 119 region.flush(true); 120 } 121 122 HStore store = region.getStore(fam); 123 assertEquals(fileCount, store.getStorefilesCount()); 124 125 Collection<HStoreFile> storefiles = store.getStorefiles(); 126 // None of the files should be in compacted state. 127 for (HStoreFile file : storefiles) { 128 assertFalse(file.isCompactedAway()); 129 } 130 // Do compaction 131 region.compact(true); 132 133 // now run the cleaner with a concurrent close 134 Thread cleanerThread = new Thread() { 135 @Override 136 public void run() { 137 cleaner.chore(); 138 } 139 }; 140 cleanerThread.start(); 141 // wait for cleaner to pause 142 synchronized (archived) { 143 if (!archived.get()) { 144 archived.wait(); 145 } 146 } 147 final AtomicReference<Exception> closeException = new AtomicReference<>(); 148 Thread closeThread = new Thread() { 149 @Override 150 public void run() { 151 // wait for the chore to complete and call close 152 try { 153 ((HRegion) region).close(); 154 } catch (IOException e) { 155 closeException.set(e); 156 } 157 } 158 }; 159 closeThread.start(); 160 // no error should occur after the execution of the test 161 closeThread.join(); 162 cleanerThread.join(); 163 164 if (closeException.get() != null) { 165 throw closeException.get(); 166 } 167 } 168 169 private HRegion initHRegion(TableDescriptor htd, RegionInfo info) throws IOException { 170 Configuration conf = testUtil.getConfiguration(); 171 Path tableDir = FSUtils.getTableDir(testDir, htd.getTableName()); 172 173 HRegionFileSystem fs = 174 new WaitingHRegionFileSystem(conf, tableDir.getFileSystem(conf), tableDir, info); 175 ChunkCreator.initialize(MemStoreLABImpl.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null); 176 final Configuration walConf = new Configuration(conf); 177 FSUtils.setRootDir(walConf, tableDir); 178 final WALFactory wals = new WALFactory(walConf, "log_" + info.getEncodedName()); 179 HRegion region = new HRegion(fs, wals.getWAL(info), conf, htd, null); 180 181 region.initialize(); 182 183 return region; 184 } 185 186 private class WaitingHRegionFileSystem extends HRegionFileSystem { 187 188 public WaitingHRegionFileSystem(final Configuration conf, final FileSystem fs, 189 final Path tableDir, final RegionInfo regionInfo) { 190 super(conf, fs, tableDir, regionInfo); 191 } 192 193 @Override 194 public void removeStoreFiles(String familyName, Collection<HStoreFile> storeFiles) 195 throws IOException { 196 super.removeStoreFiles(familyName, storeFiles); 197 archived.set(true); 198 synchronized (archived) { 199 archived.notifyAll(); 200 } 201 try { 202 // unfortunately we can't use a stronger barrier here as the fix synchronizing 203 // the race condition will then block 204 Thread.sleep(100); 205 } catch (InterruptedException ie) { 206 throw new InterruptedIOException("Interrupted waiting for latch"); 207 } 208 } 209 } 210}