1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.regionserver;
19
20 import static org.apache.hadoop.hbase.KeyValueTestUtil.create;
21 import static org.apache.hadoop.hbase.regionserver.KeyValueScanFixture.scanFixture;
22 import static org.junit.Assert.assertTrue;
23
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
28 import java.util.NavigableSet;
29 import java.util.Random;
30 import java.util.TreeSet;
31 import java.util.UUID;
32 import java.util.concurrent.CountDownLatch;
33
34 import org.apache.hadoop.conf.Configuration;
35 import org.apache.hadoop.fs.FileSystem;
36 import org.apache.hadoop.fs.Path;
37 import org.apache.hadoop.hbase.HBaseConfiguration;
38 import org.apache.hadoop.hbase.HBaseTestingUtility;
39 import org.apache.hadoop.hbase.HColumnDescriptor;
40 import org.apache.hadoop.hbase.HRegionInfo;
41 import org.apache.hadoop.hbase.HTableDescriptor;
42 import org.apache.hadoop.hbase.KeepDeletedCells;
43 import org.apache.hadoop.hbase.KeyValue;
44 import org.apache.hadoop.hbase.TableName;
45 import org.apache.hadoop.hbase.client.Scan;
46 import org.apache.hadoop.hbase.io.hfile.CacheConfig;
47 import org.apache.hadoop.hbase.io.hfile.HFileContext;
48 import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
49 import org.apache.hadoop.hbase.io.hfile.RandomKeyValueUtil;
50 import org.apache.hadoop.hbase.testclassification.MediumTests;
51 import org.apache.hadoop.hbase.testclassification.RegionServerTests;
52 import org.apache.hadoop.hbase.util.Bytes;
53 import org.junit.BeforeClass;
54 import org.junit.Rule;
55 import org.junit.Test;
56 import org.junit.experimental.categories.Category;
57 import org.junit.rules.TestName;
58
59
60
61
62
63
64 @Category({ RegionServerTests.class, MediumTests.class })
65 public class TestStoreScannerClosure {
66
67 private static final int NUM_VALID_KEY_TYPES = KeyValue.Type.values().length - 2;
68 @Rule
69 public TestName name = new TestName();
70 private static final String CF_STR = "cf";
71 private static HRegion region;
72 private static final byte[] CF = Bytes.toBytes(CF_STR);
73 static Configuration CONF = HBaseConfiguration.create();
74 private static CacheConfig cacheConf;
75 private static FileSystem fs;
76 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
77 private ScanInfo scanInfo = new ScanInfo(CONF, CF, 0, Integer.MAX_VALUE,
78 Long.MAX_VALUE, KeepDeletedCells.FALSE, 0, KeyValue.COMPARATOR);
79 private final static byte[] fam = Bytes.toBytes("cf_1");
80 private static final KeyValue[] kvs =
81 new KeyValue[] { create("R1", "cf", "a", 11, KeyValue.Type.Put, "dont-care"),
82 create("R1", "cf", "b", 11, KeyValue.Type.Put, "dont-care"),
83 create("R1", "cf", "c", 11, KeyValue.Type.Put, "dont-care"),
84 create("R1", "cf", "d", 11, KeyValue.Type.Put, "dont-care"),
85 create("R1", "cf", "e", 11, KeyValue.Type.Put, "dont-care"),
86 create("R1", "cf", "f", 11, KeyValue.Type.Put, "dont-care"),
87 create("R1", "cf", "g", 11, KeyValue.Type.Put, "dont-care"),
88 create("R1", "cf", "h", 11, KeyValue.Type.Put, "dont-care"),
89 create("R1", "cf", "i", 11, KeyValue.Type.Put, "dont-care"),
90 create("R2", "cf", "a", 11, KeyValue.Type.Put, "dont-care"), };
91
92 @BeforeClass
93 public static void setUp() throws Exception {
94 CONF = TEST_UTIL.getConfiguration();
95 cacheConf = new CacheConfig(CONF);
96 fs = TEST_UTIL.getTestFileSystem();
97 TableName tableName = TableName.valueOf("test");
98 HTableDescriptor htd = new HTableDescriptor(tableName);
99 htd.addFamily(new HColumnDescriptor(fam));
100 HRegionInfo info = new HRegionInfo(tableName, null, null, false);
101 Path path = TEST_UTIL.getDataTestDir("test");
102 region = HBaseTestingUtility.createRegionAndWAL(info, path, path,
103 TEST_UTIL.getConfiguration(), htd);
104 }
105
106 @Test
107 public void testScannerCloseAndUpdateReaders1() throws Exception {
108 testScannerCloseAndUpdateReaderInternal(true, false);
109 }
110
111 @Test
112 public void testScannerCloseAndUpdateReaders2() throws Exception {
113 testScannerCloseAndUpdateReaderInternal(false, true);
114 }
115
116 private Path writeStoreFile() throws IOException {
117 Path storeFileParentDir = new Path(TEST_UTIL.getDataTestDir(), "TestHFile");
118 Path storeFilePath = new Path(storeFileParentDir,
119 UUID.randomUUID().toString().replaceAll("-", ""));
120 HFileContext meta = new HFileContextBuilder().withBlockSize(64 * 1024).build();
121 StoreFile.Writer writer = new StoreFile.WriterBuilder(CONF, cacheConf, fs)
122 .withFilePath(storeFilePath)
123 .withFileContext(meta)
124 .build();
125 final int rowLen = 32;
126 Random RNG = new Random();
127 for (int i = 0; i < 1000; ++i) {
128 byte[] k = RandomKeyValueUtil.randomOrderedKey(RNG, i);
129 byte[] v = RandomKeyValueUtil.randomValue(RNG);
130 int cfLen = RNG.nextInt(k.length - rowLen + 1);
131 KeyValue kv = new KeyValue(k, 0, rowLen, k, rowLen, cfLen, k, rowLen + cfLen,
132 k.length - rowLen - cfLen, RNG.nextLong(), generateKeyType(RNG), v, 0, v.length);
133 writer.append(kv);
134 }
135 writer.close();
136 return writer.getPath();
137 }
138
139 private static KeyValue.Type generateKeyType(Random rand) {
140 if (rand.nextBoolean()) {
141
142 return KeyValue.Type.Put;
143 } else {
144 KeyValue.Type keyType = KeyValue.Type.values()[1 + rand.nextInt(NUM_VALID_KEY_TYPES)];
145 if (keyType == KeyValue.Type.Minimum || keyType == KeyValue.Type.Maximum) {
146 throw new RuntimeException("Generated an invalid key type: " + keyType + ". "
147 + "Probably the layout of KeyValue.Type has changed.");
148 }
149 return keyType;
150 }
151 }
152
153 private StoreFile readStoreFile(Path storeFilePath, Configuration conf) throws Exception {
154
155 StoreFile file = new StoreFile(fs, storeFilePath, conf, cacheConf, BloomType.NONE);
156 return file;
157 }
158
159 private void testScannerCloseAndUpdateReaderInternal(final boolean awaitUpdate,
160 final boolean awaitClose) throws IOException, InterruptedException {
161
162 Path path = writeStoreFile();
163 StoreFile file = null;
164 final List<StoreFile> files = new ArrayList<StoreFile>();
165 try {
166 file = readStoreFile(path, CONF);
167 files.add(file);
168 } catch (Exception e) {
169
170 assertTrue(false);
171 }
172 scanFixture(kvs);
173
174 try (ExtendedStoreScanner scan = new ExtendedStoreScanner(region.getStore(fam), scanInfo,
175 new Scan(), getCols("a", "d"), 100L)) {
176 Thread closeThread = new Thread() {
177 public void run() {
178 scan.close(awaitClose, true);
179 }
180 };
181 closeThread.start();
182 Thread updateThread = new Thread() {
183 public void run() {
184 try {
185 scan.updateReaders(awaitUpdate, files, Collections.<KeyValueScanner>emptyList());
186 } catch (IOException e) {
187 e.printStackTrace();
188 }
189 }
190 };
191 updateThread.start();
192
193 closeThread.join();
194
195 updateThread.join();
196 if (file.getReader() != null) {
197
198
199 int refCount = file.getReader().getRefCount();
200 assertTrue("The store scanner count should be 0", refCount == 0);
201 }
202 }
203 }
204
205 private static class ExtendedStoreScanner extends StoreScanner {
206 private CountDownLatch latch = new CountDownLatch(1);
207
208 public ExtendedStoreScanner(Store store, ScanInfo scanInfo, Scan scan,
209 NavigableSet<byte[]> columns, long readPt) throws IOException {
210 super(store, scanInfo, scan, columns, readPt);
211 }
212
213 public void updateReaders(boolean await, List<StoreFile> sfs,
214 List<KeyValueScanner> memStoreScanners) throws IOException {
215 if (await) {
216 try {
217 latch.await();
218 } catch (InterruptedException e) {
219 e.printStackTrace();
220 }
221 }
222 super.updateReaders(sfs, memStoreScanners);
223 if (!await) {
224 latch.countDown();
225 }
226 }
227
228
229 public void close(boolean await, boolean dummy) {
230 if (await) {
231 try {
232 latch.await();
233 } catch (InterruptedException e) {
234 e.printStackTrace();
235 }
236 }
237 super.close();
238 if (!await) {
239 latch.countDown();
240 }
241 }
242 }
243
244 NavigableSet<byte[]> getCols(String... strCols) {
245 NavigableSet<byte[]> cols = new TreeSet<>(Bytes.BYTES_COMPARATOR);
246 for (String col : strCols) {
247 byte[] bytes = Bytes.toBytes(col);
248 cols.add(bytes);
249 }
250 return cols;
251 }
252 }