private void flushRegions() throws ForeignException {
    if (regions.isEmpty()) {
      // No regions on this RS, we are basically done.
      return;
    }

    monitor.rethrowException();

    // assert that the taskManager is empty.
    if (taskManager.hasTasks()) {
      throw new IllegalStateException(
        "Attempting to flush " + table + " but we currently have outstanding tasks");
    }

    List<byte[]> familiesToFlush = null;
    if (families != null && !families.isEmpty()) {
      LOG.debug("About to flush family {} on all regions for table {}", families, table);
      familiesToFlush = families.stream().map(Bytes::toBytes).collect(Collectors.toList());
    }
    // Add all hfiles already existing in region.
    for (HRegion region : regions) {
      // submit one task per region for parallelize by region.
      taskManager.submitTask(new RegionFlushTask(region, familiesToFlush));
      monitor.rethrowException();
    }

    // wait for everything to complete.
    LOG.debug("Flush region tasks submitted for " + regions.size() + " regions");
    try {
      taskManager.waitForOutstandingTasks();
    } catch (InterruptedException e) {
      throw new ForeignException(getMemberName(), e);
    }
  }
This flush region implementation uses the distributed procedure framework to flush table regions.
Its acquireBarrier stage does nothing. Its insideBarrier stage flushes the regions.
hbase-server/src/main/java/org/apache/hadoop/hbase/procedure/flush/FlushTableSubprocedure.java