@Test
  public void testMutateRowInParallel() throws Exception {
    final int numReaderThreads = 100;
    final CountDownLatch latch = new CountDownLatch(numReaderThreads);

    final byte[] row = Bytes.toBytes("row");
    final byte[] q1 = Bytes.toBytes("q1");
    final byte[] q2 = Bytes.toBytes("q2");
    final byte[] q3 = Bytes.toBytes("q3");
    final byte[] q4 = Bytes.toBytes("q4");
    final String v1 = "v1";
    final String v2 = "v2";

    // We need to ensure the timestamp of the delete operation is more than the previous one
    final AtomicLong deleteTimestamp = new AtomicLong();

    region = initHRegion(tableName, method, CONF, fam1);

    // Initial values
    region.batchMutate(new Mutation[] { new Put(row).addColumn(fam1, q1, Bytes.toBytes(v1))
      .addColumn(fam1, q2, deleteTimestamp.getAndIncrement(), Bytes.toBytes(v2))
      .addColumn(fam1, q3, Bytes.toBytes(1L)).addColumn(fam1, q4, Bytes.toBytes("a")) });

    final AtomicReference<AssertionError> assertionError = new AtomicReference<>();

    // Writer thread
    Thread writerThread = new Thread(() -> {
      try {
        while (true) {
          // If all the reader threads finish, then stop the writer thread
          if (latch.await(0, TimeUnit.MILLISECONDS)) {
            return;
          }

          // Execute the mutations. This should be done atomically
          region.mutateRow(new RowMutations(row)
            .add(Arrays.asList(new Put(row).addColumn(fam1, q1, Bytes.toBytes(v2)),
              new Delete(row).addColumns(fam1, q2, deleteTimestamp.getAndIncrement()),
              new Increment(row).addColumn(fam1, q3, 1L),
              new Append(row).addColumn(fam1, q4, Bytes.toBytes("b")))));

          // We need to ensure the timestamps of the Increment/Append operations are more than the
          // previous ones
          Result result = region.get(new Get(row).addColumn(fam1, q3).addColumn(fam1, q4));
          long tsIncrement = result.getColumnLatestCell(fam1, q3).getTimestamp();
          long tsAppend = result.getColumnLatestCell(fam1, q4).getTimestamp();

          // Put the initial values
          region.batchMutate(new Mutation[] { new Put(row).addColumn(fam1, q1, Bytes.toBytes(v1))
            .addColumn(fam1, q2, deleteTimestamp.getAndIncrement(), Bytes.toBytes(v2))
            .addColumn(fam1, q3, tsIncrement + 1, Bytes.toBytes(1L))
            .addColumn(fam1, q4, tsAppend + 1, Bytes.toBytes("a")) });
        }
      } catch (Exception e) {
        assertionError.set(new AssertionError(e));
      }
    });
    writerThread.start();

    // Reader threads
    for (int i = 0; i < numReaderThreads; i++) {
      new Thread(() -> {
        try {
          for (int j = 0; j < 10000; j++) {
            // Verify the values
            Result result = region.get(new Get(row));

            // The values should be equals to either the initial values or the values after
            // executing the mutations
            String q1Value = Bytes.toString(result.getValue(fam1, q1));
            if (v1.equals(q1Value)) {
              assertEquals(v2, Bytes.toString(result.getValue(fam1, q2)));
              assertEquals(1L, Bytes.toLong(result.getValue(fam1, q3)));
              assertEquals("a", Bytes.toString(result.getValue(fam1, q4)));
            } else if (v2.equals(q1Value)) {
              assertNull(Bytes.toString(result.getValue(fam1, q2)));
              assertEquals(2L, Bytes.toLong(result.getValue(fam1, q3)));
              assertEquals("ab", Bytes.toString(result.getValue(fam1, q4)));
            } else {
              fail("the qualifier " + Bytes.toString(q1) + " should be " + v1 + " or " + v2
                + ", but " + q1Value);
            }
          }
        } catch (Exception e) {
          assertionError.set(new AssertionError(e));
        } catch (AssertionError e) {
          assertionError.set(e);
        }

        latch.countDown();
      }).start();
    }

    writerThread.join();

    if (assertionError.get() != null) {
      throw assertionError.get();
    }
  }
HBASE-16429 Make sure no stuck if roll writer when ring buffer is filled with appends
@throws IOException if IO error occurred during test
hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java