public static void testRollbackAndDoubleExecution(
    final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId, final int lastStep,
    boolean waitForAsyncProcs) throws Exception {
    // Execute up to last step
    testRecoveryAndDoubleExecution(procExec, procId, lastStep, false);

    // Restart the executor and rollback the step twice
    // rollback step N - kill before store update
    // restart executor/store
    // rollback step N - save on store
    InjectAbortOnLoadListener abortListener = new InjectAbortOnLoadListener(procExec);
    abortListener.addProcId(procId);
    procExec.registerListener(abortListener);
    try {
      for (int i = 0; !procExec.isFinished(procId); ++i) {
        LOG.info("Restart " + i + " rollback state: " + procExec.getProcedure(procId));
        ProcedureTestingUtility.assertProcNotYetCompleted(procExec, procId);
        restartMasterProcedureExecutor(procExec);
        ProcedureTestingUtility.waitProcedure(procExec, procId);
      }
    } finally {
      assertTrue(procExec.unregisterListener(abortListener));
    }

    if (waitForAsyncProcs) {
      // Sometimes there are other procedures still executing (including asynchronously spawned by
      // procId) and due to KillAndToggleBeforeStoreUpdate flag ProcedureExecutor is stopped before
      // store update. Let all pending procedures finish normally.
      ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, false);
      // check 3 times to confirm that the procedure executor has not been killed
      for (int i = 0; i < 3; i++) {
        if (!procExec.isRunning()) {
          LOG.warn("ProcedureExecutor not running, may have been stopped by pending procedure due"
            + " to KillAndToggleBeforeStoreUpdate flag.");
          restartMasterProcedureExecutor(procExec);
          break;
        }
        Thread.sleep(1000);
      }
      ProcedureTestingUtility.waitNoProcedureRunning(procExec);
    }

    assertEquals(true, procExec.isRunning());
    ProcedureTestingUtility.assertIsAbortException(procExec.getResult(procId));
  }
Execute the procedure up to "lastStep" and then the ProcedureExecutor is restarted and an
abort() is injected. If the procedure implement abort() this should result in rollback being
triggered. Each rollback step is called twice, by restarting the executor after every step. At
the end of this call the procedure should be finished and rolledback. This method assert on the
procedure being terminated with an AbortException.
hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureTestingUtility.java