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.apache.hadoop.hbase.HBaseTestingUtility.fam1; 021import static org.apache.hadoop.hbase.HBaseTestingUtility.fam2; 022import static org.junit.Assert.assertEquals; 023import static org.junit.Assert.assertNotNull; 024import static org.junit.Assert.assertTrue; 025import static org.junit.Assert.fail; 026 027import java.io.IOException; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.List; 031import java.util.Objects; 032import java.util.Random; 033import java.util.concurrent.CountDownLatch; 034import java.util.concurrent.atomic.AtomicInteger; 035import java.util.concurrent.atomic.AtomicLong; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.fs.FileSystem; 038import org.apache.hadoop.fs.Path; 039import org.apache.hadoop.hbase.Cell; 040import org.apache.hadoop.hbase.CellUtil; 041import org.apache.hadoop.hbase.CompareOperator; 042import org.apache.hadoop.hbase.HBaseClassTestRule; 043import org.apache.hadoop.hbase.HBaseTestingUtility; 044import org.apache.hadoop.hbase.HColumnDescriptor; 045import org.apache.hadoop.hbase.HConstants; 046import org.apache.hadoop.hbase.HRegionInfo; 047import org.apache.hadoop.hbase.HTableDescriptor; 048import org.apache.hadoop.hbase.MultithreadedTestUtil; 049import org.apache.hadoop.hbase.MultithreadedTestUtil.TestContext; 050import org.apache.hadoop.hbase.MultithreadedTestUtil.TestThread; 051import org.apache.hadoop.hbase.TableName; 052import org.apache.hadoop.hbase.client.Append; 053import org.apache.hadoop.hbase.client.Delete; 054import org.apache.hadoop.hbase.client.Durability; 055import org.apache.hadoop.hbase.client.Get; 056import org.apache.hadoop.hbase.client.Increment; 057import org.apache.hadoop.hbase.client.IsolationLevel; 058import org.apache.hadoop.hbase.client.Mutation; 059import org.apache.hadoop.hbase.client.Put; 060import org.apache.hadoop.hbase.client.RegionInfo; 061import org.apache.hadoop.hbase.client.Result; 062import org.apache.hadoop.hbase.client.RowMutations; 063import org.apache.hadoop.hbase.client.Scan; 064import org.apache.hadoop.hbase.client.TableDescriptor; 065import org.apache.hadoop.hbase.filter.BinaryComparator; 066import org.apache.hadoop.hbase.io.HeapSize; 067import org.apache.hadoop.hbase.io.hfile.BlockCache; 068import org.apache.hadoop.hbase.io.hfile.CacheConfig; 069import org.apache.hadoop.hbase.testclassification.LargeTests; 070import org.apache.hadoop.hbase.testclassification.VerySlowRegionServerTests; 071import org.apache.hadoop.hbase.util.Bytes; 072import org.apache.hadoop.hbase.wal.WAL; 073import org.junit.After; 074import org.junit.Before; 075import org.junit.ClassRule; 076import org.junit.Rule; 077import org.junit.Test; 078import org.junit.experimental.categories.Category; 079import org.junit.rules.TestName; 080import org.slf4j.Logger; 081import org.slf4j.LoggerFactory; 082 083/** 084 * Testing of HRegion.incrementColumnValue, HRegion.increment, 085 * and HRegion.append 086 */ 087@Category({VerySlowRegionServerTests.class, LargeTests.class}) // Starts 100 threads 088public class TestAtomicOperation { 089 090 @ClassRule 091 public static final HBaseClassTestRule CLASS_RULE = 092 HBaseClassTestRule.forClass(TestAtomicOperation.class); 093 094 private static final Logger LOG = LoggerFactory.getLogger(TestAtomicOperation.class); 095 @Rule public TestName name = new TestName(); 096 097 HRegion region = null; 098 private HBaseTestingUtility TEST_UTIL = HBaseTestingUtility.createLocalHTU(); 099 100 // Test names 101 static byte[] tableName; 102 static final byte[] qual1 = Bytes.toBytes("qual1"); 103 static final byte[] qual2 = Bytes.toBytes("qual2"); 104 static final byte[] qual3 = Bytes.toBytes("qual3"); 105 static final byte[] value1 = Bytes.toBytes("value1"); 106 static final byte[] value2 = Bytes.toBytes("value2"); 107 static final byte [] row = Bytes.toBytes("rowA"); 108 static final byte [] row2 = Bytes.toBytes("rowB"); 109 110 @Before 111 public void setup() { 112 tableName = Bytes.toBytes(name.getMethodName()); 113 } 114 115 @After 116 public void teardown() throws IOException { 117 if (region != null) { 118 CacheConfig cacheConfig = region.getStores().get(0).getCacheConfig(); 119 region.close(); 120 WAL wal = region.getWAL(); 121 if (wal != null) { 122 wal.close(); 123 } 124 cacheConfig.getBlockCache().ifPresent(BlockCache::shutdown); 125 region = null; 126 } 127 } 128 129 ////////////////////////////////////////////////////////////////////////////// 130 // New tests that doesn't spin up a mini cluster but rather just test the 131 // individual code pieces in the HRegion. 132 ////////////////////////////////////////////////////////////////////////////// 133 134 /** 135 * Test basic append operation. 136 * More tests in 137 * @see org.apache.hadoop.hbase.client.TestFromClientSide#testAppend() 138 */ 139 @Test 140 public void testAppend() throws IOException { 141 initHRegion(tableName, name.getMethodName(), fam1); 142 String v1 = "Ultimate Answer to the Ultimate Question of Life,"+ 143 " The Universe, and Everything"; 144 String v2 = " is... 42."; 145 Append a = new Append(row); 146 a.setReturnResults(false); 147 a.addColumn(fam1, qual1, Bytes.toBytes(v1)); 148 a.addColumn(fam1, qual2, Bytes.toBytes(v2)); 149 assertTrue(region.append(a, HConstants.NO_NONCE, HConstants.NO_NONCE).isEmpty()); 150 a = new Append(row); 151 a.addColumn(fam1, qual1, Bytes.toBytes(v2)); 152 a.addColumn(fam1, qual2, Bytes.toBytes(v1)); 153 Result result = region.append(a, HConstants.NO_NONCE, HConstants.NO_NONCE); 154 assertEquals(0, Bytes.compareTo(Bytes.toBytes(v1+v2), result.getValue(fam1, qual1))); 155 assertEquals(0, Bytes.compareTo(Bytes.toBytes(v2+v1), result.getValue(fam1, qual2))); 156 } 157 158 @Test 159 public void testAppendWithMultipleFamilies() throws IOException { 160 final byte[] fam3 = Bytes.toBytes("colfamily31"); 161 initHRegion(tableName, name.getMethodName(), fam1, fam2, fam3); 162 String v1 = "Appended"; 163 String v2 = "Value"; 164 165 Append a = new Append(row); 166 a.setReturnResults(false); 167 a.addColumn(fam1, qual1, Bytes.toBytes(v1)); 168 a.addColumn(fam2, qual2, Bytes.toBytes(v2)); 169 Result result = region.append(a, HConstants.NO_NONCE, HConstants.NO_NONCE); 170 assertTrue("Expected an empty result but result contains " + result.size() + " keys", 171 result.isEmpty()); 172 173 a = new Append(row); 174 a.addColumn(fam2, qual2, Bytes.toBytes(v1)); 175 a.addColumn(fam1, qual1, Bytes.toBytes(v2)); 176 a.addColumn(fam3, qual3, Bytes.toBytes(v2)); 177 a.addColumn(fam1, qual2, Bytes.toBytes(v1)); 178 179 result = region.append(a, HConstants.NO_NONCE, HConstants.NO_NONCE); 180 181 byte[] actualValue1 = result.getValue(fam1, qual1); 182 byte[] actualValue2 = result.getValue(fam2, qual2); 183 byte[] actualValue3 = result.getValue(fam3, qual3); 184 byte[] actualValue4 = result.getValue(fam1, qual2); 185 186 assertNotNull("Value1 should bot be null", actualValue1); 187 assertNotNull("Value2 should bot be null", actualValue2); 188 assertNotNull("Value3 should bot be null", actualValue3); 189 assertNotNull("Value4 should bot be null", actualValue4); 190 assertEquals(0, Bytes.compareTo(Bytes.toBytes(v1 + v2), actualValue1)); 191 assertEquals(0, Bytes.compareTo(Bytes.toBytes(v2 + v1), actualValue2)); 192 assertEquals(0, Bytes.compareTo(Bytes.toBytes(v2), actualValue3)); 193 assertEquals(0, Bytes.compareTo(Bytes.toBytes(v1), actualValue4)); 194 } 195 196 @Test 197 public void testAppendWithNonExistingFamily() throws IOException { 198 initHRegion(tableName, name.getMethodName(), fam1); 199 final String v1 = "Value"; 200 final Append a = new Append(row); 201 a.addColumn(fam1, qual1, Bytes.toBytes(v1)); 202 a.addColumn(fam2, qual2, Bytes.toBytes(v1)); 203 Result result = null; 204 try { 205 result = region.append(a, HConstants.NO_NONCE, HConstants.NO_NONCE); 206 fail("Append operation should fail with NoSuchColumnFamilyException."); 207 } catch (NoSuchColumnFamilyException e) { 208 assertEquals(null, result); 209 } catch (Exception e) { 210 fail("Append operation should fail with NoSuchColumnFamilyException."); 211 } 212 } 213 214 @Test 215 public void testIncrementWithNonExistingFamily() throws IOException { 216 initHRegion(tableName, name.getMethodName(), fam1); 217 final Increment inc = new Increment(row); 218 inc.addColumn(fam1, qual1, 1); 219 inc.addColumn(fam2, qual2, 1); 220 inc.setDurability(Durability.ASYNC_WAL); 221 try { 222 region.increment(inc, HConstants.NO_NONCE, HConstants.NO_NONCE); 223 } catch (NoSuchColumnFamilyException e) { 224 final Get g = new Get(row); 225 final Result result = region.get(g); 226 assertEquals(null, result.getValue(fam1, qual1)); 227 assertEquals(null, result.getValue(fam2, qual2)); 228 } catch (Exception e) { 229 fail("Increment operation should fail with NoSuchColumnFamilyException."); 230 } 231 } 232 233 /** 234 * Test multi-threaded increments. 235 */ 236 @Test 237 public void testIncrementMultiThreads() throws IOException { 238 boolean fast = true; 239 LOG.info("Starting test testIncrementMultiThreads"); 240 // run a with mixed column families (1 and 3 versions) 241 initHRegion(tableName, name.getMethodName(), new int[] {1,3}, fam1, fam2); 242 243 // Create 100 threads, each will increment by its own quantity. All 100 threads update the 244 // same row over two column families. 245 int numThreads = 100; 246 int incrementsPerThread = 1000; 247 Incrementer[] all = new Incrementer[numThreads]; 248 int expectedTotal = 0; 249 // create all threads 250 for (int i = 0; i < numThreads; i++) { 251 all[i] = new Incrementer(region, i, i, incrementsPerThread); 252 expectedTotal += (i * incrementsPerThread); 253 } 254 255 // run all threads 256 for (int i = 0; i < numThreads; i++) { 257 all[i].start(); 258 } 259 260 // wait for all threads to finish 261 for (int i = 0; i < numThreads; i++) { 262 try { 263 all[i].join(); 264 } catch (InterruptedException e) { 265 LOG.info("Ignored", e); 266 } 267 } 268 assertICV(row, fam1, qual1, expectedTotal, fast); 269 assertICV(row, fam1, qual2, expectedTotal*2, fast); 270 assertICV(row, fam2, qual3, expectedTotal*3, fast); 271 LOG.info("testIncrementMultiThreads successfully verified that total is " + expectedTotal); 272 } 273 274 275 private void assertICV(byte [] row, 276 byte [] familiy, 277 byte[] qualifier, 278 long amount, 279 boolean fast) throws IOException { 280 // run a get and see? 281 Get get = new Get(row); 282 if (fast) get.setIsolationLevel(IsolationLevel.READ_UNCOMMITTED); 283 get.addColumn(familiy, qualifier); 284 Result result = region.get(get); 285 assertEquals(1, result.size()); 286 287 Cell kv = result.rawCells()[0]; 288 long r = Bytes.toLong(CellUtil.cloneValue(kv)); 289 assertEquals(amount, r); 290 } 291 292 private void initHRegion (byte [] tableName, String callingMethod, 293 byte[] ... families) 294 throws IOException { 295 initHRegion(tableName, callingMethod, null, families); 296 } 297 298 private void initHRegion (byte [] tableName, String callingMethod, int [] maxVersions, 299 byte[] ... families) 300 throws IOException { 301 HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(tableName)); 302 int i=0; 303 for(byte [] family : families) { 304 HColumnDescriptor hcd = new HColumnDescriptor(family); 305 hcd.setMaxVersions(maxVersions != null ? maxVersions[i++] : 1); 306 htd.addFamily(hcd); 307 } 308 HRegionInfo info = new HRegionInfo(htd.getTableName(), null, null, false); 309 region = TEST_UTIL.createLocalHRegion(info, htd); 310 } 311 312 /** 313 * A thread that makes increment calls always on the same row, this.row against two column 314 * families on this row. 315 */ 316 public static class Incrementer extends Thread { 317 318 private final Region region; 319 private final int numIncrements; 320 private final int amount; 321 322 323 public Incrementer(Region region, int threadNumber, int amount, int numIncrements) { 324 super("Incrementer." + threadNumber); 325 this.region = region; 326 this.numIncrements = numIncrements; 327 this.amount = amount; 328 setDaemon(true); 329 } 330 331 @Override 332 public void run() { 333 for (int i = 0; i < numIncrements; i++) { 334 try { 335 Increment inc = new Increment(row); 336 inc.addColumn(fam1, qual1, amount); 337 inc.addColumn(fam1, qual2, amount*2); 338 inc.addColumn(fam2, qual3, amount*3); 339 inc.setDurability(Durability.ASYNC_WAL); 340 Result result = region.increment(inc); 341 if (result != null) { 342 assertEquals(Bytes.toLong(result.getValue(fam1, qual1))*2, 343 Bytes.toLong(result.getValue(fam1, qual2))); 344 assertTrue(result.getValue(fam2, qual3) != null); 345 assertEquals(Bytes.toLong(result.getValue(fam1, qual1))*3, 346 Bytes.toLong(result.getValue(fam2, qual3))); 347 assertEquals(Bytes.toLong(result.getValue(fam1, qual1))*2, 348 Bytes.toLong(result.getValue(fam1, qual2))); 349 long fam1Increment = Bytes.toLong(result.getValue(fam1, qual1))*3; 350 long fam2Increment = Bytes.toLong(result.getValue(fam2, qual3)); 351 assertEquals("fam1=" + fam1Increment + ", fam2=" + fam2Increment, 352 fam1Increment, fam2Increment); 353 } 354 } catch (IOException e) { 355 e.printStackTrace(); 356 } 357 } 358 } 359 } 360 361 @Test 362 public void testAppendMultiThreads() throws IOException { 363 LOG.info("Starting test testAppendMultiThreads"); 364 // run a with mixed column families (1 and 3 versions) 365 initHRegion(tableName, name.getMethodName(), new int[] {1,3}, fam1, fam2); 366 367 int numThreads = 100; 368 int opsPerThread = 100; 369 AtomicOperation[] all = new AtomicOperation[numThreads]; 370 final byte[] val = new byte[]{1}; 371 372 AtomicInteger failures = new AtomicInteger(0); 373 // create all threads 374 for (int i = 0; i < numThreads; i++) { 375 all[i] = new AtomicOperation(region, opsPerThread, null, failures) { 376 @Override 377 public void run() { 378 for (int i=0; i<numOps; i++) { 379 try { 380 Append a = new Append(row); 381 a.addColumn(fam1, qual1, val); 382 a.addColumn(fam1, qual2, val); 383 a.addColumn(fam2, qual3, val); 384 a.setDurability(Durability.ASYNC_WAL); 385 region.append(a, HConstants.NO_NONCE, HConstants.NO_NONCE); 386 387 Get g = new Get(row); 388 Result result = region.get(g); 389 assertEquals(result.getValue(fam1, qual1).length, result.getValue(fam1, qual2).length); 390 assertEquals(result.getValue(fam1, qual1).length, result.getValue(fam2, qual3).length); 391 } catch (IOException e) { 392 e.printStackTrace(); 393 failures.incrementAndGet(); 394 fail(); 395 } 396 } 397 } 398 }; 399 } 400 401 // run all threads 402 for (int i = 0; i < numThreads; i++) { 403 all[i].start(); 404 } 405 406 // wait for all threads to finish 407 for (int i = 0; i < numThreads; i++) { 408 try { 409 all[i].join(); 410 } catch (InterruptedException e) { 411 } 412 } 413 assertEquals(0, failures.get()); 414 Get g = new Get(row); 415 Result result = region.get(g); 416 assertEquals(10000, result.getValue(fam1, qual1).length); 417 assertEquals(10000, result.getValue(fam1, qual2).length); 418 assertEquals(10000, result.getValue(fam2, qual3).length); 419 } 420 /** 421 * Test multi-threaded row mutations. 422 */ 423 @Test 424 public void testRowMutationMultiThreads() throws IOException { 425 LOG.info("Starting test testRowMutationMultiThreads"); 426 initHRegion(tableName, name.getMethodName(), fam1); 427 428 // create 10 threads, each will alternate between adding and 429 // removing a column 430 int numThreads = 10; 431 int opsPerThread = 250; 432 AtomicOperation[] all = new AtomicOperation[numThreads]; 433 434 AtomicLong timeStamps = new AtomicLong(0); 435 AtomicInteger failures = new AtomicInteger(0); 436 // create all threads 437 for (int i = 0; i < numThreads; i++) { 438 all[i] = new AtomicOperation(region, opsPerThread, timeStamps, failures) { 439 @Override 440 public void run() { 441 boolean op = true; 442 for (int i=0; i<numOps; i++) { 443 try { 444 // throw in some flushes 445 if (i%10==0) { 446 synchronized(region) { 447 LOG.debug("flushing"); 448 region.flush(true); 449 if (i%100==0) { 450 region.compact(false); 451 } 452 } 453 } 454 long ts = timeStamps.incrementAndGet(); 455 RowMutations rm = new RowMutations(row); 456 if (op) { 457 Put p = new Put(row, ts); 458 p.addColumn(fam1, qual1, value1); 459 p.setDurability(Durability.ASYNC_WAL); 460 rm.add(p); 461 Delete d = new Delete(row); 462 d.addColumns(fam1, qual2, ts); 463 d.setDurability(Durability.ASYNC_WAL); 464 rm.add(d); 465 } else { 466 Delete d = new Delete(row); 467 d.addColumns(fam1, qual1, ts); 468 d.setDurability(Durability.ASYNC_WAL); 469 rm.add(d); 470 Put p = new Put(row, ts); 471 p.addColumn(fam1, qual2, value2); 472 p.setDurability(Durability.ASYNC_WAL); 473 rm.add(p); 474 } 475 region.mutateRow(rm); 476 op ^= true; 477 // check: should always see exactly one column 478 Get g = new Get(row); 479 Result r = region.get(g); 480 if (r.size() != 1) { 481 LOG.debug(Objects.toString(r)); 482 failures.incrementAndGet(); 483 fail(); 484 } 485 } catch (IOException e) { 486 e.printStackTrace(); 487 failures.incrementAndGet(); 488 fail(); 489 } 490 } 491 } 492 }; 493 } 494 495 // run all threads 496 for (int i = 0; i < numThreads; i++) { 497 all[i].start(); 498 } 499 500 // wait for all threads to finish 501 for (int i = 0; i < numThreads; i++) { 502 try { 503 all[i].join(); 504 } catch (InterruptedException e) { 505 } 506 } 507 assertEquals(0, failures.get()); 508 } 509 510 511 /** 512 * Test multi-threaded region mutations. 513 */ 514 @Test 515 public void testMultiRowMutationMultiThreads() throws IOException { 516 517 LOG.info("Starting test testMultiRowMutationMultiThreads"); 518 initHRegion(tableName, name.getMethodName(), fam1); 519 520 // create 10 threads, each will alternate between adding and 521 // removing a column 522 int numThreads = 10; 523 int opsPerThread = 250; 524 AtomicOperation[] all = new AtomicOperation[numThreads]; 525 526 AtomicLong timeStamps = new AtomicLong(0); 527 AtomicInteger failures = new AtomicInteger(0); 528 final List<byte[]> rowsToLock = Arrays.asList(row, row2); 529 // create all threads 530 for (int i = 0; i < numThreads; i++) { 531 all[i] = new AtomicOperation(region, opsPerThread, timeStamps, failures) { 532 @Override 533 public void run() { 534 boolean op = true; 535 for (int i=0; i<numOps; i++) { 536 try { 537 // throw in some flushes 538 if (i%10==0) { 539 synchronized(region) { 540 LOG.debug("flushing"); 541 region.flush(true); 542 if (i%100==0) { 543 region.compact(false); 544 } 545 } 546 } 547 long ts = timeStamps.incrementAndGet(); 548 List<Mutation> mrm = new ArrayList<>(); 549 if (op) { 550 Put p = new Put(row2, ts); 551 p.addColumn(fam1, qual1, value1); 552 p.setDurability(Durability.ASYNC_WAL); 553 mrm.add(p); 554 Delete d = new Delete(row); 555 d.addColumns(fam1, qual1, ts); 556 d.setDurability(Durability.ASYNC_WAL); 557 mrm.add(d); 558 } else { 559 Delete d = new Delete(row2); 560 d.addColumns(fam1, qual1, ts); 561 d.setDurability(Durability.ASYNC_WAL); 562 mrm.add(d); 563 Put p = new Put(row, ts); 564 p.setDurability(Durability.ASYNC_WAL); 565 p.addColumn(fam1, qual1, value2); 566 mrm.add(p); 567 } 568 region.mutateRowsWithLocks(mrm, rowsToLock, HConstants.NO_NONCE, HConstants.NO_NONCE); 569 op ^= true; 570 // check: should always see exactly one column 571 Scan s = new Scan(row); 572 RegionScanner rs = region.getScanner(s); 573 List<Cell> r = new ArrayList<>(); 574 while (rs.next(r)) 575 ; 576 rs.close(); 577 if (r.size() != 1) { 578 LOG.debug(Objects.toString(r)); 579 failures.incrementAndGet(); 580 fail(); 581 } 582 } catch (IOException e) { 583 e.printStackTrace(); 584 failures.incrementAndGet(); 585 fail(); 586 } 587 } 588 } 589 }; 590 } 591 592 // run all threads 593 for (int i = 0; i < numThreads; i++) { 594 all[i].start(); 595 } 596 597 // wait for all threads to finish 598 for (int i = 0; i < numThreads; i++) { 599 try { 600 all[i].join(); 601 } catch (InterruptedException e) { 602 } 603 } 604 assertEquals(0, failures.get()); 605 } 606 607 public static class AtomicOperation extends Thread { 608 protected final HRegion region; 609 protected final int numOps; 610 protected final AtomicLong timeStamps; 611 protected final AtomicInteger failures; 612 protected final Random r = new Random(); 613 614 public AtomicOperation(HRegion region, int numOps, AtomicLong timeStamps, 615 AtomicInteger failures) { 616 this.region = region; 617 this.numOps = numOps; 618 this.timeStamps = timeStamps; 619 this.failures = failures; 620 } 621 } 622 623 private static CountDownLatch latch = new CountDownLatch(1); 624 private enum TestStep { 625 INIT, // initial put of 10 to set value of the cell 626 PUT_STARTED, // began doing a put of 50 to cell 627 PUT_COMPLETED, // put complete (released RowLock, but may not have advanced MVCC). 628 CHECKANDPUT_STARTED, // began checkAndPut: if 10 -> 11 629 CHECKANDPUT_COMPLETED // completed checkAndPut 630 // NOTE: at the end of these steps, the value of the cell should be 50, not 11! 631 } 632 private static volatile TestStep testStep = TestStep.INIT; 633 private final String family = "f1"; 634 635 /** 636 * Test written as a verifier for HBASE-7051, CheckAndPut should properly read 637 * MVCC. 638 * 639 * Moved into TestAtomicOperation from its original location, TestHBase7051 640 */ 641 @Test 642 public void testPutAndCheckAndPutInParallel() throws Exception { 643 Configuration conf = TEST_UTIL.getConfiguration(); 644 conf.setClass(HConstants.REGION_IMPL, MockHRegion.class, HeapSize.class); 645 HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(name.getMethodName())) 646 .addFamily(new HColumnDescriptor(family)); 647 this.region = TEST_UTIL.createLocalHRegion(htd, null, null); 648 Put[] puts = new Put[1]; 649 Put put = new Put(Bytes.toBytes("r1")); 650 put.addColumn(Bytes.toBytes(family), Bytes.toBytes("q1"), Bytes.toBytes("10")); 651 puts[0] = put; 652 653 region.batchMutate(puts, HConstants.NO_NONCE, HConstants.NO_NONCE); 654 MultithreadedTestUtil.TestContext ctx = 655 new MultithreadedTestUtil.TestContext(conf); 656 ctx.addThread(new PutThread(ctx, region)); 657 ctx.addThread(new CheckAndPutThread(ctx, region)); 658 ctx.startThreads(); 659 while (testStep != TestStep.CHECKANDPUT_COMPLETED) { 660 Thread.sleep(100); 661 } 662 ctx.stop(); 663 Scan s = new Scan(); 664 RegionScanner scanner = region.getScanner(s); 665 List<Cell> results = new ArrayList<>(); 666 ScannerContext scannerContext = ScannerContext.newBuilder().setBatchLimit(2).build(); 667 scanner.next(results, scannerContext); 668 for (Cell keyValue : results) { 669 assertEquals("50",Bytes.toString(CellUtil.cloneValue(keyValue))); 670 } 671 } 672 673 private class PutThread extends TestThread { 674 private Region region; 675 PutThread(TestContext ctx, Region region) { 676 super(ctx); 677 this.region = region; 678 } 679 680 @Override 681 public void doWork() throws Exception { 682 Put[] puts = new Put[1]; 683 Put put = new Put(Bytes.toBytes("r1")); 684 put.addColumn(Bytes.toBytes(family), Bytes.toBytes("q1"), Bytes.toBytes("50")); 685 puts[0] = put; 686 testStep = TestStep.PUT_STARTED; 687 region.batchMutate(puts); 688 } 689 } 690 691 private class CheckAndPutThread extends TestThread { 692 private Region region; 693 CheckAndPutThread(TestContext ctx, Region region) { 694 super(ctx); 695 this.region = region; 696 } 697 698 @Override 699 public void doWork() throws Exception { 700 Put[] puts = new Put[1]; 701 Put put = new Put(Bytes.toBytes("r1")); 702 put.addColumn(Bytes.toBytes(family), Bytes.toBytes("q1"), Bytes.toBytes("11")); 703 puts[0] = put; 704 while (testStep != TestStep.PUT_COMPLETED) { 705 Thread.sleep(100); 706 } 707 testStep = TestStep.CHECKANDPUT_STARTED; 708 region.checkAndMutate(Bytes.toBytes("r1"), Bytes.toBytes(family), Bytes.toBytes("q1"), 709 CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes("10")), put); 710 testStep = TestStep.CHECKANDPUT_COMPLETED; 711 } 712 } 713 714 public static class MockHRegion extends HRegion { 715 716 public MockHRegion(Path tableDir, WAL log, FileSystem fs, Configuration conf, 717 final RegionInfo regionInfo, final TableDescriptor htd, RegionServerServices rsServices) { 718 super(tableDir, log, fs, conf, regionInfo, htd, rsServices); 719 } 720 721 @Override 722 public RowLock getRowLockInternal(final byte[] row, boolean readLock, 723 final RowLock prevRowlock) throws IOException { 724 if (testStep == TestStep.CHECKANDPUT_STARTED) { 725 latch.countDown(); 726 } 727 return new WrappedRowLock(super.getRowLockInternal(row, readLock, null)); 728 } 729 730 public class WrappedRowLock implements RowLock { 731 732 private final RowLock rowLock; 733 734 private WrappedRowLock(RowLock rowLock) { 735 this.rowLock = rowLock; 736 } 737 738 739 @Override 740 public void release() { 741 if (testStep == TestStep.INIT) { 742 this.rowLock.release(); 743 return; 744 } 745 746 if (testStep == TestStep.PUT_STARTED) { 747 try { 748 testStep = TestStep.PUT_COMPLETED; 749 this.rowLock.release(); 750 // put has been written to the memstore and the row lock has been released, but the 751 // MVCC has not been advanced. Prior to fixing HBASE-7051, the following order of 752 // operations would cause the non-atomicity to show up: 753 // 1) Put releases row lock (where we are now) 754 // 2) CheckAndPut grabs row lock and reads the value prior to the put (10) 755 // because the MVCC has not advanced 756 // 3) Put advances MVCC 757 // So, in order to recreate this order, we wait for the checkAndPut to grab the rowLock 758 // (see below), and then wait some more to give the checkAndPut time to read the old 759 // value. 760 latch.await(); 761 Thread.sleep(1000); 762 } catch (InterruptedException e) { 763 Thread.currentThread().interrupt(); 764 } 765 } 766 else if (testStep == TestStep.CHECKANDPUT_STARTED) { 767 this.rowLock.release(); 768 } 769 } 770 } 771 } 772}