@Override
    public void update(Procedure<?> proc) {
      // inject a sleep to simulate the scenario in HBASE-28210
      if (proc.hasParent() && proc.getStackIndexes() != null) {
        int lastStackId = proc.getStackIndexes()[proc.getStackIndexes().length - 1];
        try {
          // sleep more times if the stack id is smaller
          Thread.sleep(100L * (10 - lastStackId));
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
          return;
        }
        // simulate the failure when updating the second sub procedure
        if (!updated.compareAndSet(false, true)) {
          procExec.stop();
          throw new RuntimeException("inject error");
        }
      }
      synchronized (procMap) {
        try {
          procMap.put(proc.getProcId(), ProcedureUtil.convertToProtoProcedure(proc));
        } catch (IOException e) {
          throw new UncheckedIOException(e);
        }
      }
    }
Testcase for HBASE-28210, where we persist the procedure which has been inserted later to
{@link RootProcedureState} first and then crash, and then cause holes in stack ids when loading,
and finally fail the start up of master.
hbase-procedure/src/test/java/org/apache/hadoop/hbase/procedure2/TestStackIdHoles.java